From e5672a85bab932d8ef4f2d9163744323b3b0c2c0 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 12 Nov 2025 21:57:53 +0800 Subject: [PATCH 01/30] feat(provider): supoprt certsan match pattern in deployment to volcengine live --- .../tencentcloud-cdn/tencentcloud_cdn_test.go | 7 +- .../tencentcloud_ecdn_test.go | 7 +- .../tencentcloud-eo/tencentcloud_eo_test.go | 9 +-- .../volcengine-cdn/volcengine_cdn_test.go | 7 +- .../providers/volcengine-live/consts.go | 2 + .../volcengine-live/volcengine_live.go | 68 ++++++++++++++++++- .../volcengine-live/volcengine_live_test.go | 7 +- .../volcengine-live/volcengine_live.go | 5 +- ...NodeConfigFieldsProviderVolcEngineLive.tsx | 27 +++++--- .../i18n/locales/zh/nls.workflow.nodes.json | 2 +- 10 files changed, 109 insertions(+), 32 deletions(-) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go index b9dfe8c31..6d4948054 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - Domain: fDomain, + SecretId: fSecretId, + SecretKey: fSecretKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go index ac6eac0e6..6a219492d 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - Domain: fDomain, + SecretId: fSecretId, + SecretKey: fSecretKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go index 51a440c1b..1c3321d5c 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - ZoneId: fZoneId, - Domains: strings.Split(fDomains, ";"), + SecretId: fSecretId, + SecretKey: fSecretKey, + ZoneId: fZoneId, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domains: strings.Split(fDomains, ";"), }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn_test.go b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn_test.go index 0d83f202e..5209d2da3 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/volcengine-live/consts.go b/pkg/core/ssl-deployer/providers/volcengine-live/consts.go index 8665260fc..84801f8d6 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/consts.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/consts.go @@ -5,4 +5,6 @@ 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/volcengine-live/volcengine_live.go b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go index 418308d31..5f381f1dd 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -2,6 +2,7 @@ package volcenginelive import ( "context" + "crypto/x509" "errors" "fmt" "log/slog" @@ -12,6 +13,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" xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) @@ -110,6 +112,21 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } } + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + temp, err := d.getMatchedDomainsByCertX509(ctx, certX509) + if err != nil { + return nil, err + } + + domains = temp + } + default: return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.DomainMatchPattern) } @@ -144,7 +161,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w 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 + // REF: https://www.volcengine.com/docs/6469/1126815 listDomainDetailPageNum := int32(1) listDomainDetailPageSize := int32(1000) for { @@ -187,9 +204,56 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w return domains, nil } +func (d *SSLDeployerProvider) getMatchedDomainsByCertX509(ctx context.Context, certX509 *x509.Certificate) ([]string, error) { + domains := make([]string, 0) + + // 遍历查询域名列表,获取匹配的域名 + // REF: https://www.volcengine.com/docs/6469/1126815 + 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 certX509.VerifyHostname(domain.Domain) == nil { + 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 + // REF: https://www.volcengine.com/docs/6469/1126820 bindCertReq := &velive.BindCertBody{ ChainID: cloudCertId, Domain: domain, diff --git a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live_test.go b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live_test.go index ce123dc2b..e03bbe152 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live_test.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go b/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go index 3fafa0c11..284403cb4 100644 --- a/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go @@ -94,13 +94,12 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 上传新证书 // REF: https://www.volcengine.com/docs/6469/1186278#%E6%B7%BB%E5%8A%A0%E8%AF%81%E4%B9%A6 createCertReq := &velive.CreateCertBody{ - CertName: ve.String(certName), - UseWay: "https", - ProjectName: ve.String("default"), + CertName: ve.String(certName), Rsa: velive.CreateCertBodyRsa{ Prikey: privkeyPEM, Pubkey: certPEM, }, + UseWay: "https", } createCertResp, err := m.sdkClient.CreateCert(ctx, createCertReq) m.logger.Debug("sdk request 'live.CreateCert'", slog.Any("request", createCertReq), slog.Any("response", createCertResp)) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineLive.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineLive.tsx index 4e4801ebf..9be9aad11 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineLive.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineLive.tsx @@ -3,12 +3,14 @@ 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 BizDeployNodeConfigFieldsProviderVolcEngineLive = () => { const { i18n, t } = useTranslation(); @@ -18,8 +20,11 @@ const BizDeployNodeConfigFieldsProviderVolcEngineLive = () => { [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 ( <> { rules={[formRule]} > ({ + options={[DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN].map((s) => ({ key: s, label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), value: s, @@ -37,14 +42,16 @@ const BizDeployNodeConfigFieldsProviderVolcEngineLive = () => { /> - - - + + + + + ); }; @@ -62,7 +69,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z .object({ domainMatchPattern: z.enum( - [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD], + [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN], t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") ), domain: z.string().nullish(), diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 4e1485515..152bbd418 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -196,7 +196,7 @@ "workflow_node.deploy.form.shared_domain_match_pattern.placeholder": "请选择域名匹配模式", "workflow_node.deploy.form.shared_domain_match_pattern.option.exact.label": "精确匹配", "workflow_node.deploy.form.shared_domain_match_pattern.option.wildcard.label": "通配符匹配(泛域名)", - "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "通过证书自动匹配", + "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "根据证书自动匹配", "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "注意:对于支持泛解析的站点,精确匹配仅适用于该泛解析站点本身,而不包括符合条件的子域名。", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启 1Panel 服务", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel 子节点名称(可选)", From eafb8cb20f0db1ec0a1b47ad224595919b8c094b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 12 Nov 2025 21:57:53 +0800 Subject: [PATCH 02/30] feat(provider): supoprt certsan match pattern in deployment to tencentcloud edgeone --- .../tencentcloud-cdn/tencentcloud_cdn.go | 2 +- .../tencentcloud-ecdn/tencentcloud_ecdn.go | 2 +- .../providers/tencentcloud-eo/consts.go | 2 + .../tencentcloud-eo/tencentcloud_eo.go | 35 +++++-- .../volcengine-cdn/volcengine_cdn.go | 4 +- .../volcengine-live/volcengine_live.go | 77 ++++----------- ...odeConfigFieldsProviderTencentCloudCDN.tsx | 8 +- ...deConfigFieldsProviderTencentCloudECDN.tsx | 8 +- ...NodeConfigFieldsProviderTencentCloudEO.tsx | 93 +++++++++++++------ ...yNodeConfigFieldsProviderVolcEngineCDN.tsx | 8 +- 10 files changed, 135 insertions(+), 104 deletions(-) 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 960022a60..06a89fb76 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -130,7 +130,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } default: - return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 遍历更新域名证书 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 98c78d022..85a8ba9c6 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -130,7 +130,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } default: - return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 遍历更新域名证书 diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/consts.go index 026b4b08f..d4312c86f 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/consts.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/consts.go @@ -5,4 +5,6 @@ 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-eo/tencentcloud_eo.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go index 5eafbd905..0367b2ca9 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go @@ -15,6 +15,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/tencentcloud-eo/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/tencentcloud-ssl" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) @@ -86,9 +87,6 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if d.config.ZoneId == "" { return nil, errors.New("config `zoneId` is required") } - if len(d.config.Domains) == 0 { - return nil, errors.New("config `domains` is required") - } // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) @@ -115,12 +113,12 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domains` is required") } - domainsInZone, err := d.getDomainsInZone(ctx, d.config.ZoneId) + domainCandidates, err := d.getAllDomainsInZone(ctx, d.config.ZoneId) if err != nil { return nil, err } - domains = lo.Filter(domainsInZone, func(domain string, _ int) bool { + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { for _, configDomain := range d.config.Domains { if xcerthostname.IsMatch(configDomain, domain) { return true @@ -128,14 +126,33 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } return false }) - if len(domains) == 0 { - return nil, errors.New("no domains matched in wildcard mode") + return nil, errors.New("no domains matched by wildcard") + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomainsInZone(ctx, d.config.ZoneId) + 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 match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 配置域名证书 @@ -154,7 +171,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } -func (d *SSLDeployerProvider) getDomainsInZone(ctx context.Context, zoneId string) ([]string, error) { +func (d *SSLDeployerProvider) getAllDomainsInZone(ctx context.Context, zoneId string) ([]string, error) { var domainsInZone []string const pageSize = 200 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 0389d1f2a..a9e59b2cb 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -124,7 +124,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } default: - return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 遍历绑定证书 @@ -225,7 +225,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, clo if len(domains) == 0 { if len(describeCertConfigResp.SpecifiedCertConfig) == 0 { - return nil, errors.New("domains not found") + return nil, errors.New("no domains matched by certificate") } } 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 5f381f1dd..656006cb0 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -2,12 +2,12 @@ package volcenginelive import ( "context" - "crypto/x509" "errors" "fmt" "log/slog" "strings" + "github.com/samber/lo" velive "github.com/volcengine/volc-sdk-golang/service/live/v20230101" ve "github.com/volcengine/volcengine-go-sdk/volcengine" @@ -101,12 +101,17 @@ 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.getAllDomains(ctx) if err != nil { return nil, err } - domains = temp + 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 = append(domains, d.config.Domain) } @@ -119,16 +124,21 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, err } - temp, err := d.getMatchedDomainsByCertX509(ctx, certX509) + domainCandidates, err := d.getAllDomains(ctx) if err != nil { return nil, err } - domains = temp + 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 match pattern: '%s'", d.config.DomainMatchPattern) + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } // 遍历绑定证书 @@ -157,7 +167,7 @@ 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) { +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) // 遍历查询域名列表,获取匹配的域名 @@ -184,9 +194,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w if listDomainDetailResp.Result.DomainList != nil { for _, domain := range listDomainDetailResp.Result.DomainList { - if xcerthostname.IsMatch(wildcardDomain, domain.Domain) { - domains = append(domains, domain.Domain) - } + domains = append(domains, domain.Domain) } } @@ -198,54 +206,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w } if len(domains) == 0 { - return nil, errors.New("domain not found") - } - - return domains, nil -} - -func (d *SSLDeployerProvider) getMatchedDomainsByCertX509(ctx context.Context, certX509 *x509.Certificate) ([]string, error) { - domains := make([]string, 0) - - // 遍历查询域名列表,获取匹配的域名 - // REF: https://www.volcengine.com/docs/6469/1126815 - 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 certX509.VerifyHostname(domain.Domain) == nil { - 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 nil, errors.New("no domains matched by wildcard") } return domains, nil diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCDN.tsx index 072b97e41..9be40f444 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCDN.tsx @@ -41,7 +41,13 @@ const BizDeployNodeConfigFieldsProviderTencentCloudCDN = () => { name={[parentNamePath, "domainMatchPattern"]} initialValue={initialValues.domainMatchPattern} label={t("workflow_node.deploy.form.shared_domain_match_pattern.label")} - extra={} + extra={ + fieldDomainMatchPattern === DOMAIN_MATCH_PATTERN_EXACT ? ( + + ) : ( + void 0 + ) + } rules={[formRule]} > { name={[parentNamePath, "domainMatchPattern"]} initialValue={initialValues.domainMatchPattern} label={t("workflow_node.deploy.form.shared_domain_match_pattern.label")} - extra={} + extra={ + fieldDomainMatchPattern === DOMAIN_MATCH_PATTERN_EXACT ? ( + + ) : ( + void 0 + ) + } rules={[formRule]} > { const { i18n, t } = useTranslation(); @@ -20,8 +23,11 @@ const BizDeployNodeConfigFieldsProviderTencentCloudEO = () => { [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 ( <> { name={[parentNamePath, "domainMatchPattern"]} initialValue={initialValues.domainMatchPattern} label={t("workflow_node.deploy.form.shared_domain_match_pattern.label")} - extra={} + extra={ + fieldDomainMatchPattern === DOMAIN_MATCH_PATTERN_EXACT ? ( + + ) : ( + void 0 + ) + } rules={[formRule]} > ({ + options={[DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN].map((s) => ({ key: s, label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), value: s, @@ -60,20 +72,22 @@ const BizDeployNodeConfigFieldsProviderTencentCloudEO = () => { /> - - - + + + + + ); }; @@ -89,20 +103,39 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - endpoint: z.string().nullish(), - zoneId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder")), - domainMatchPattern: z.enum( - [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD], - t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") - ), - domains: z.string().refine((v) => { - if (!v) return false; - return String(v) - .split(MULTIPLE_INPUT_SEPARATOR) - .every((e) => validDomainName(e, { allowWildcard: true })); - }, t("common.errmsg.domain_invalid")), - }); + return z + .object({ + endpoint: z.string().nullish(), + zoneId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder")), + domainMatchPattern: z.enum( + [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN], + t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") + ), + domains: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + const v = + values.domains && + String(values.domains) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => validDomainName(e, { allowWildcard: true })); + if (!v) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domains"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderTencentCloudEO, { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineCDN.tsx index 9ecf338d8..7f4424858 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineCDN.tsx @@ -31,7 +31,13 @@ const BizDeployNodeConfigFieldsProviderVolcEngineCDN = () => { name={[parentNamePath, "domainMatchPattern"]} initialValue={initialValues.domainMatchPattern} label={t("workflow_node.deploy.form.shared_domain_match_pattern.label")} - extra={} + extra={ + fieldDomainMatchPattern === DOMAIN_MATCH_PATTERN_EXACT ? ( + + ) : ( + void 0 + ) + } rules={[formRule]} > Date: Wed, 12 Nov 2025 21:57:53 +0800 Subject: [PATCH 03/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to aliyun cdn --- .../certdeploy/deployers/sp_aliyun_cdn.go | 11 +- .../providers/aliyun-cdn/aliyun_cdn.go | 150 ++++++++++++++++-- .../providers/aliyun-cdn/aliyun_cdn_test.go | 7 +- .../providers/aliyun-cdn/consts.go | 10 ++ .../providers/aliyun-cdn/internal/client.go | 90 +++++++++++ .../huaweicloud-elb/huaweicloud_elb.go | 6 +- .../tencentcloud-cdn/tencentcloud_cdn.go | 4 +- .../tencentcloud-clb/tencentcloud_clb.go | 6 +- .../tencentcloud-eo/tencentcloud_eo.go | 1 + .../tencentcloud-gaap/tencentcloud_gaap.go | 4 +- .../volcengine-cdn/volcengine_cdn.go | 4 +- .../volcengine-live/volcengine_live.go | 6 +- ...eployNodeConfigFieldsProviderAliyunCDN.tsx | 74 +++++++-- .../i18n/locales/en/nls.workflow.nodes.json | 2 +- 14 files changed, 332 insertions(+), 43 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/aliyun-cdn/consts.go diff --git a/internal/certdeploy/deployers/sp_aliyun_cdn.go b/internal/certdeploy/deployers/sp_aliyun_cdn.go index 82631c2d2..a24bad5be 100644 --- a/internal/certdeploy/deployers/sp_aliyun_cdn.go +++ b/internal/certdeploy/deployers/sp_aliyun_cdn.go @@ -17,11 +17,12 @@ func init() { } provider, err := aliyuncdn.NewSSLDeployerProvider(&aliyuncdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + 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 917360669..b822db0a0 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -17,6 +17,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-cdn/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -28,6 +30,9 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -80,10 +85,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -92,16 +93,145 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // "*.example.com" → ".example.com",适配阿里云 CDN 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + // "*.example.com" → ".example.com",适配阿里云 CDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + 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") + } + } + + 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 + + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certId); 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://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-describeuserdomains + describeUserDomainsPageNumber := int32(1) + describeUserDomainsPageSize := int32(500) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeUserDomainsReq := &alicdn.DescribeUserDomainsRequest{ + CheckDomainShow: tea.Bool(true), + PageNumber: tea.Int32(describeUserDomainsPageNumber), + PageSize: tea.Int32(describeUserDomainsPageSize), + } + describeUserDomainsResp, err := d.sdkClient.DescribeUserDomainsWithContext(ctx, describeUserDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'cdn.DescribeUserDomains'", slog.Any("request", describeUserDomainsReq), slog.Any("response", describeUserDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeUserDomains': %w", err) + } + + for _, domain := range describeUserDomainsResp.Body.Domains.PageData { + status := tea.StringValue(domain.DomainStatus) + if status == "stopping" || status == "deleting" { + continue + } + + domains = append(domains, tea.StringValue(domain.DomainName)) + } + + if len(describeUserDomainsResp.Body.Domains.PageData) < int(describeUserDomainsPageNumber) { + break + } else { + describeUserDomainsPageNumber++ + } + } + + 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, cloudCertId int64) error { // 设置 CDN 域名域名证书 // REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate - certId, _ := strconv.ParseInt(upres.CertId, 10, 64) setCdnDomainSSLCertificateReq := &alicdn.SetCdnDomainSSLCertificateRequest{ DomainName: tea.String(domain), CertType: tea.String("cas"), - CertId: tea.Int64(certId), + CertId: tea.Int64(cloudCertId), CertRegion: lo. If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")). Else(tea.String("ap-southeast-1")), @@ -110,10 +240,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificateWithContext(context.TODO(), setCdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cdn.SetCdnDomainSSLCertificate'", slog.Any("request", setCdnDomainSSLCertificateReq), slog.Any("response", setCdnDomainSSLCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret string) (*internal.CdnClient, error) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn_test.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn_test.go index 271edc3d3..664d9a721 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-cdn/consts.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/consts.go new file mode 100644 index 000000000..618edec4d --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/consts.go @@ -0,0 +1,10 @@ +package aliyuncdn + +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/aliyun-cdn/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/internal/client.go index 1f921cd16..34b49f184 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/internal/client.go @@ -35,6 +35,96 @@ func (client *CdnClient) Init(config *openapiutil.Config) (_err error) { return nil } +func (client *CdnClient) DescribeUserDomainsWithContext(ctx context.Context, request *alicdn.DescribeUserDomainsRequest, runtime *dara.RuntimeOptions) (_result *alicdn.DescribeUserDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.CdnType) { + query["CdnType"] = request.CdnType + } + + if !dara.IsNil(request.ChangeEndTime) { + query["ChangeEndTime"] = request.ChangeEndTime + } + + if !dara.IsNil(request.ChangeStartTime) { + query["ChangeStartTime"] = request.ChangeStartTime + } + + if !dara.IsNil(request.CheckDomainShow) { + query["CheckDomainShow"] = request.CheckDomainShow + } + + if !dara.IsNil(request.Coverage) { + query["Coverage"] = request.Coverage + } + + if !dara.IsNil(request.DomainName) { + query["DomainName"] = request.DomainName + } + + if !dara.IsNil(request.DomainSearchType) { + query["DomainSearchType"] = request.DomainSearchType + } + + if !dara.IsNil(request.DomainStatus) { + query["DomainStatus"] = request.DomainStatus + } + + if !dara.IsNil(request.OwnerId) { + query["OwnerId"] = request.OwnerId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.ResourceGroupId) { + query["ResourceGroupId"] = request.ResourceGroupId + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Source) { + query["Source"] = request.Source + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeUserDomains"), + Version: dara.String("2018-05-10"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alicdn.DescribeUserDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *CdnClient) SetCdnDomainSSLCertificateWithContext(ctx context.Context, request *alicdn.SetCdnDomainSSLCertificateRequest, runtime *dara.RuntimeOptions) (_result *alicdn.SetCdnDomainSSLCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go index f324e9e0d..2801de666 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -217,7 +217,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM case <-ctx.Done(): return ctx.Err() default: - if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil { + if err := d.updateListenerCertificate(ctx, listenerId, upres.CertId); err != nil { errs = append(errs, err) } } @@ -245,14 +245,14 @@ func (d *SSLDeployerProvider) deployToListener(ctx context.Context, certPEM stri } // 更新监听器证书 - if err := d.modifyListenerCertificate(ctx, d.config.ListenerId, upres.CertId); err != nil { + if err := d.updateListenerCertificate(ctx, d.config.ListenerId, upres.CertId); err != nil { return err } return nil } -func (d *SSLDeployerProvider) modifyListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { // 查询监听器详情 // REF: https://support.huaweicloud.com/api-elb/ShowListener.html showListenerReq := &hcelbmodel.ShowListenerRequest{ 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 06a89fb76..059c2eaf9 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -145,7 +145,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.updateDomainHttpsServerCert(ctx, domain, upres.CertId); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -229,7 +229,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, clo return domains, nil } -func (d *SSLDeployerProvider) updateDomainHttpsServerCert(ctx context.Context, domain string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 查询域名详细配置 // REF: https://cloud.tencent.com/document/api/228/41117 describeDomainsConfigReq := tccdn.NewDescribeDomainsConfigRequest() diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go index a76f50cfa..d56fba961 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -159,7 +159,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer case <-ctx.Done(): return ctx.Err() default: - if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil { + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil { errs = append(errs, err) } } @@ -182,7 +182,7 @@ func (d *SSLDeployerProvider) deployToListener(ctx context.Context, cloudCertId } // 更新监听器证书 - if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, d.config.ListenerId, cloudCertId); err != nil { + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, d.config.ListenerId, cloudCertId); err != nil { return err } @@ -246,7 +246,7 @@ func (d *SSLDeployerProvider) deployToRuleDomain(ctx context.Context, cloudCertI return nil } -func (d *SSLDeployerProvider) modifyListenerCertificate(ctx context.Context, cloudLoadbalancerId, cloudListenerId, cloudCertId string) error { +func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, cloudLoadbalancerId, cloudListenerId, cloudCertId string) error { // 查询负载均衡的监听器列表 // REF: https://cloud.tencent.com/document/api/214/30686 describeListenersReq := tcclb.NewDescribeListenersRequest() diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go index 0367b2ca9..e0524e3c9 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go @@ -96,6 +96,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 var domains []string switch d.config.DomainMatchPattern { case "", DOMAIN_MATCH_PATTERN_EXACT: diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go b/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go index 3aa13b81c..f3bb9ffab 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go @@ -111,14 +111,14 @@ func (d *SSLDeployerProvider) deployToListener(ctx context.Context, cloudCertId } // 更新监听器证书 - if err := d.modifyHttpsListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil { + if err := d.updateHttpsListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil { return err } return nil } -func (d *SSLDeployerProvider) modifyHttpsListenerCertificate(ctx context.Context, cloudListenerId, cloudCertId string) error { +func (d *SSLDeployerProvider) updateHttpsListenerCertificate(ctx context.Context, cloudListenerId, cloudCertId string) error { // 查询 HTTPS 监听器信息 // REF: https://cloud.tencent.com/document/api/608/37001 describeHTTPSListenersReq := tcgaap.NewDescribeHTTPSListenersRequest() 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 a9e59b2cb..1b154f376 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -139,7 +139,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.bindCert(ctx, domain, upres.CertId); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -232,7 +232,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, clo return domains, nil } -func (d *SSLDeployerProvider) bindCert(ctx context.Context, domain string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 关联证书与加速域名 // REF: https://www.volcengine.com/docs/6454/125712 batchDeployCertReq := &vecdn.BatchDeployCertInput{ 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 656006cb0..e39675d3b 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -153,7 +153,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.bindCert(ctx, domain, upres.CertId); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -170,7 +170,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表,获取匹配的域名 + // 遍历查询域名列表 // REF: https://www.volcengine.com/docs/6469/1126815 listDomainDetailPageNum := int32(1) listDomainDetailPageSize := int32(1000) @@ -212,7 +212,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return domains, nil } -func (d *SSLDeployerProvider) bindCert(ctx context.Context, domain string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 绑定证书 // REF: https://www.volcengine.com/docs/6469/1126820 bindCertReq := &velive.BindCertBody{ diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx index 6f813dc20..455120533 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx @@ -1,12 +1,17 @@ import { getI18n, useTranslation } from "react-i18next"; -import { AutoComplete, Form, Input } from "antd"; +import { AutoComplete, 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 BizDeployNodeConfigFieldsProviderAliyunCDN = () => { const { i18n, t } = useTranslation(); @@ -15,8 +20,11 @@ const BizDeployNodeConfigFieldsProviderAliyunCDN = () => { [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 ( <> { + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -48,6 +80,7 @@ const BizDeployNodeConfigFieldsProviderAliyunCDN = () => { const getInitialValues = (): Nullish>> => { return { region: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -55,10 +88,33 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - region: z.string().nullish(), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + region: z.string().nullish(), + domainMatchPattern: z.enum( + [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN], + t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") + ), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderAliyunCDN, { diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 6d7760855..27317d614 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -198,7 +198,7 @@ "workflow_node.deploy.form.shared_domain_match_pattern.option.exact.label": "Exact matches", "workflow_node.deploy.form.shared_domain_match_pattern.option.wildcard.label": "Wildcard matches", "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "via Certificate", - "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "Notes: For sites that support wildcards, exact matches will only apply to the site itself and will not include its subdomains.", + "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "Notes: For sites that support wildcards, \"Exact matches\" will only apply to the site itself, not include its subdomains.", "workflow_node.deploy.form.1panel_console_auto_restart.label": "Auto restart 1Panel after deployment", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel node name (Optional)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "Please enter 1Panel node name", From a812f8748ed456126e04ec8303b7b293d4781162 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 12 Nov 2025 21:57:53 +0800 Subject: [PATCH 04/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to aliyun dcdn --- .../certdeploy/deployers/sp_aliyun_dcdn.go | 11 +- .../providers/aliyun-dcdn/aliyun_dcdn.go | 150 ++++++++++++++++-- .../providers/aliyun-dcdn/aliyun_dcdn_test.go | 7 +- .../providers/aliyun-dcdn/consts.go | 10 ++ .../providers/aliyun-dcdn/internal/client.go | 86 ++++++++++ ...eployNodeConfigFieldsProviderAliyunCDN.tsx | 5 +- ...ployNodeConfigFieldsProviderAliyunDCDN.tsx | 71 +++++++-- ...odeConfigFieldsProviderTencentCloudCDN.tsx | 5 +- ...deConfigFieldsProviderTencentCloudECDN.tsx | 5 +- ...NodeConfigFieldsProviderTencentCloudEO.tsx | 5 +- ...yNodeConfigFieldsProviderVolcEngineCDN.tsx | 5 +- ...NodeConfigFieldsProviderVolcEngineLive.tsx | 5 +- 12 files changed, 314 insertions(+), 51 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/aliyun-dcdn/consts.go diff --git a/internal/certdeploy/deployers/sp_aliyun_dcdn.go b/internal/certdeploy/deployers/sp_aliyun_dcdn.go index 03556df6c..6855d337b 100644 --- a/internal/certdeploy/deployers/sp_aliyun_dcdn.go +++ b/internal/certdeploy/deployers/sp_aliyun_dcdn.go @@ -17,11 +17,12 @@ func init() { } provider, err := aliyundcdn.NewSSLDeployerProvider(&aliyundcdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index f34278488..8e2ef7446 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -17,6 +17,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-dcdn/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -28,6 +30,9 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -80,10 +85,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -92,16 +93,145 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + // "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + 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") + } + } + + 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 dcdn domains to deploy") + } else { + d.logger.Info("found dcdn domains to deploy", slog.Any("domains", domains)) + var errs []error + + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certId); 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://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-describedcdnuserdomains + describeUserDomainsPageNumber := int32(1) + describeUserDomainsPageSize := int32(500) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeDcdnUserDomainsReq := &alidcdn.DescribeDcdnUserDomainsRequest{ + CheckDomainShow: tea.Bool(true), + PageNumber: tea.Int32(describeUserDomainsPageNumber), + PageSize: tea.Int32(describeUserDomainsPageSize), + } + describeDcdnUserDomainsResp, err := d.sdkClient.DescribeDcdnUserDomainsWithContext(ctx, describeDcdnUserDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'dcdn.DescribeDcdnUserDomains'", slog.Any("request", describeDcdnUserDomainsReq), slog.Any("response", describeDcdnUserDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'dcdn.DescribeDcdnUserDomains': %w", err) + } + + for _, domain := range describeDcdnUserDomainsResp.Body.Domains.PageData { + status := tea.StringValue(domain.DomainStatus) + if status == "stopping" || status == "deleting" { + continue + } + + domains = append(domains, tea.StringValue(domain.DomainName)) + } + + if len(describeDcdnUserDomainsResp.Body.Domains.PageData) < int(describeUserDomainsPageNumber) { + break + } else { + describeUserDomainsPageNumber++ + } + } + + 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, cloudCertId int64) error { // 配置域名证书 // REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate - certId, _ := strconv.ParseInt(upres.CertId, 10, 64) setDcdnDomainSSLCertificateReq := &alidcdn.SetDcdnDomainSSLCertificateRequest{ DomainName: tea.String(domain), CertType: tea.String("cas"), - CertId: tea.Int64(int64(certId)), + CertId: tea.Int64(cloudCertId), CertRegion: lo. If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")). Else(tea.String("ap-southeast-1")), @@ -110,10 +240,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificateWithContext(context.TODO(), setDcdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'dcdn.SetDcdnDomainSSLCertificate'", slog.Any("request", setDcdnDomainSSLCertificateReq), slog.Any("response", setDcdnDomainSSLCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret string) (*internal.DcdnClient, error) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go index 33c213c54..3f35d5634 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/consts.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/consts.go new file mode 100644 index 000000000..c7b97a047 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/consts.go @@ -0,0 +1,10 @@ +package aliyundcdn + +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/aliyun-dcdn/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/internal/client.go index fbff22aa4..8d91ef0aa 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/internal/client.go @@ -35,6 +35,92 @@ func (client *DcdnClient) Init(config *openapiutil.Config) (_err error) { return nil } +func (client *DcdnClient) DescribeDcdnUserDomainsWithContext(ctx context.Context, request *alidcdn.DescribeDcdnUserDomainsRequest, runtime *dara.RuntimeOptions) (_result *alidcdn.DescribeDcdnUserDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.ChangeEndTime) { + query["ChangeEndTime"] = request.ChangeEndTime + } + + if !dara.IsNil(request.ChangeStartTime) { + query["ChangeStartTime"] = request.ChangeStartTime + } + + if !dara.IsNil(request.CheckDomainShow) { + query["CheckDomainShow"] = request.CheckDomainShow + } + + if !dara.IsNil(request.Coverage) { + query["Coverage"] = request.Coverage + } + + if !dara.IsNil(request.DomainName) { + query["DomainName"] = request.DomainName + } + + if !dara.IsNil(request.DomainSearchType) { + query["DomainSearchType"] = request.DomainSearchType + } + + if !dara.IsNil(request.DomainStatus) { + query["DomainStatus"] = request.DomainStatus + } + + if !dara.IsNil(request.OwnerId) { + query["OwnerId"] = request.OwnerId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.ResourceGroupId) { + query["ResourceGroupId"] = request.ResourceGroupId + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + if !dara.IsNil(request.WebSiteType) { + query["WebSiteType"] = request.WebSiteType + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeDcdnUserDomains"), + Version: dara.String("2018-01-15"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alidcdn.DescribeDcdnUserDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *DcdnClient) SetDcdnDomainSSLCertificateWithContext(ctx context.Context, request *alidcdn.SetDcdnDomainSSLCertificateRequest, runtime *dara.RuntimeOptions) (_result *alidcdn.SetDcdnDomainSSLCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx index 455120533..b60ae5b48 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx @@ -91,10 +91,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z .object({ region: z.string().nullish(), - domainMatchPattern: z.enum( - [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN], - t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") - ), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), domain: z.string().nullish(), }) .superRefine((values, ctx) => { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDCDN.tsx index 43c8b1a05..aa7f69f33 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDCDN.tsx @@ -1,12 +1,17 @@ import { getI18n, useTranslation } from "react-i18next"; -import { AutoComplete, Form, Input } from "antd"; +import { AutoComplete, 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 BizDeployNodeConfigFieldsProviderAliyunDCDN = () => { const { i18n, t } = useTranslation(); @@ -15,8 +20,11 @@ const BizDeployNodeConfigFieldsProviderAliyunDCDN = () => { [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 ( <> { + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -48,6 +80,7 @@ const BizDeployNodeConfigFieldsProviderAliyunDCDN = () => { const getInitialValues = (): Nullish>> => { return { region: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -55,10 +88,30 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - region: z.string().nullish(), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + region: z.string().nullish(), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderAliyunDCDN, { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCDN.tsx index 9be40f444..0b8ebd619 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCDN.tsx @@ -86,10 +86,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z .object({ endpoint: z.string().nullish(), - domainMatchPattern: z.enum( - [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN], - t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") - ), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), domain: z.string().nullish(), }) .superRefine((values, ctx) => { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudECDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudECDN.tsx index ab8fdac8d..3e5ec1a26 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudECDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudECDN.tsx @@ -86,10 +86,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z .object({ endpoint: z.string().nullish(), - domainMatchPattern: z.enum( - [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN], - t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") - ), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), domain: z.string().nullish(), }) .superRefine((values, ctx) => { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudEO.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudEO.tsx index f47fc4ffe..922469c1d 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudEO.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudEO.tsx @@ -107,10 +107,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) .object({ endpoint: z.string().nullish(), zoneId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder")), - domainMatchPattern: z.enum( - [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN], - t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") - ), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), domains: z.string().nullish(), }) .superRefine((values, ctx) => { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineCDN.tsx index 7f4424858..fb5d053ab 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineCDN.tsx @@ -75,10 +75,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z .object({ - domainMatchPattern: z.enum( - [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN], - t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") - ), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), domain: z.string().nullish(), }) .superRefine((values, ctx) => { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineLive.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineLive.tsx index 9be9aad11..58f8d23d3 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineLive.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineLive.tsx @@ -68,10 +68,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z .object({ - domainMatchPattern: z.enum( - [DOMAIN_MATCH_PATTERN_EXACT, DOMAIN_MATCH_PATTERN_WILDCARD, DOMAIN_MATCH_PATTERN_CERTSAN], - t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder") - ), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), domain: z.string().nullish(), }) .superRefine((values, ctx) => { From 5639820bda228b39f802f63dc5c8c0fed192bee6 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 12 Nov 2025 21:57:53 +0800 Subject: [PATCH 05/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to baiducloud cdn --- .../certdeploy/deployers/sp_baiducloud_cdn.go | 7 +- .../providers/aliyun-cdn/aliyun_cdn.go | 22 +-- .../baiducloud-cdn/baiducloud_cdn.go | 137 +++++++++++++++++- .../baiducloud-cdn/baiducloud_cdn_test.go | 7 +- .../providers/baiducloud-cdn/consts.go | 10 ++ .../tencentcloud-cdn/tencentcloud_cdn.go | 12 +- .../tencentcloud-ecdn/tencentcloud_ecdn.go | 8 +- ...yNodeConfigFieldsProviderBaiduCloudCDN.tsx | 70 ++++++++- 8 files changed, 234 insertions(+), 39 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/baiducloud-cdn/consts.go diff --git a/internal/certdeploy/deployers/sp_baiducloud_cdn.go b/internal/certdeploy/deployers/sp_baiducloud_cdn.go index a32670a58..aa7d02c7e 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 b822db0a0..af88bf1d9 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 c8e181b84..362a18dfd 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 068e6fade..15ca0ec76 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 000000000..cee9f2705 --- /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 059c2eaf9..3e6fee8eb 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 85a8ba9c6..8e53534bf 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 dddbcd679..978864347 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 ( <> + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +73,30 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + region: z.string().nullish(), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderBaiduCloudCDN, { From 7cdb4024326863e1c6bd92125453cdfe63789620 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 12 Nov 2025 21:57:53 +0800 Subject: [PATCH 06/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to huaweicloud cdn --- .../deployers/sp_huaweicloud_cdn.go | 1 + .../deployers/sp_volcengine_dcdn.go | 7 +- .../providers/aliyun-cdn/aliyun_cdn.go | 1 + .../providers/aliyun-dcdn/aliyun_dcdn.go | 29 ++-- .../providers/huaweicloud-cdn/consts.go | 10 ++ .../huaweicloud-cdn/huaweicloud_cdn.go | 160 ++++++++++++++++-- .../huaweicloud-cdn/huaweicloud_cdn_test.go | 9 +- .../huaweicloud-cdn/internal/client.go | 10 ++ .../providers/jdcloud-vod/jdcloud_vod.go | 6 +- .../tencentcloud-cdn/tencentcloud_cdn.go | 6 +- .../tencentcloud-ecdn/tencentcloud_ecdn.go | 10 +- .../volcengine-cdn/volcengine_cdn.go | 16 +- .../providers/volcengine-dcdn/consts.go | 6 + .../volcengine-dcdn/volcengine_dcdn.go | 30 +++- .../volcengine-dcdn/volcengine_dcdn_test.go | 7 +- .../volcengine-live/volcengine_live.go | 4 +- ...yNodeConfigFieldsProviderBaiduCloudCDN.tsx | 1 - ...NodeConfigFieldsProviderHuaweiCloudCDN.tsx | 71 +++++++- ...NodeConfigFieldsProviderVolcEngineDCDN.tsx | 55 +++++- 19 files changed, 363 insertions(+), 76 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/huaweicloud-cdn/consts.go create mode 100644 pkg/core/ssl-deployer/providers/volcengine-dcdn/consts.go diff --git a/internal/certdeploy/deployers/sp_huaweicloud_cdn.go b/internal/certdeploy/deployers/sp_huaweicloud_cdn.go index 439325d30..349c8907f 100644 --- a/internal/certdeploy/deployers/sp_huaweicloud_cdn.go +++ b/internal/certdeploy/deployers/sp_huaweicloud_cdn.go @@ -21,6 +21,7 @@ func init() { SecretAccessKey: credentials.SecretAccessKey, EnterpriseProjectId: credentials.EnterpriseProjectId, Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/internal/certdeploy/deployers/sp_volcengine_dcdn.go b/internal/certdeploy/deployers/sp_volcengine_dcdn.go index 64d6aa74b..51d2b3530 100644 --- a/internal/certdeploy/deployers/sp_volcengine_dcdn.go +++ b/internal/certdeploy/deployers/sp_volcengine_dcdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := volcenginedcdn.NewSSLDeployerProvider(&volcenginedcdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: 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 af88bf1d9..291048bc7 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -196,6 +196,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } describeUserDomainsReq := &alicdn.DescribeUserDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), CheckDomainShow: tea.Bool(true), PageNumber: tea.Int32(describeUserDomainsPageNumber), PageSize: tea.Int32(describeUserDomainsPageSize), diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index 8e2ef7446..66e8b1149 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.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} } } @@ -192,6 +196,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } describeDcdnUserDomainsReq := &alidcdn.DescribeDcdnUserDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), CheckDomainShow: tea.Bool(true), PageNumber: tea.Int32(describeUserDomainsPageNumber), PageSize: tea.Int32(describeUserDomainsPageSize), @@ -202,13 +207,13 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'dcdn.DescribeDcdnUserDomains': %w", err) } - for _, domain := range describeDcdnUserDomainsResp.Body.Domains.PageData { - status := tea.StringValue(domain.DomainStatus) + for _, domainInfo := range describeDcdnUserDomainsResp.Body.Domains.PageData { + status := tea.StringValue(domainInfo.DomainStatus) if status == "stopping" || status == "deleting" { continue } - domains = append(domains, tea.StringValue(domain.DomainName)) + domains = append(domains, tea.StringValue(domainInfo.DomainName)) } if len(describeDcdnUserDomainsResp.Body.Domains.PageData) < int(describeUserDomainsPageNumber) { diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/consts.go new file mode 100644 index 000000000..65ac52c5c --- /dev/null +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/consts.go @@ -0,0 +1,10 @@ +package huaweicloudcdn + +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/huaweicloud-cdn/huaweicloud_cdn.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index e716d4b71..b6d658da8 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "strings" "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global" hccdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2" @@ -15,6 +16,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/huaweicloud-scm" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -26,6 +29,9 @@ type SSLDeployerProviderConfig struct { EnterpriseProjectId string `json:"enterpriseProjectId,omitempty"` // 华为云区域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -81,10 +87,6 @@ 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, fmt.Errorf("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -93,27 +95,163 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + 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, upres.CertId, upres.CertName); 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://support.huaweicloud.com/api-cdn/ListDomains.html + listDomainsPageNumber := int32(1) + listDomainsPageSize := int32(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listDomainsReq := &hccdnmodel.ListDomainsRequest{ + EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), + PageNumber: lo.ToPtr(listDomainsPageNumber), + PageSize: lo.ToPtr(listDomainsPageSize), + } + listDomainsResp, err := d.sdkClient.ListDomains(listDomainsReq) + d.logger.Debug("sdk request 'cdn.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListDomains': %w", err) + } + + if listDomainsResp.Domains != nil { + for _, domainInfo := range *listDomainsResp.Domains { + status := lo.FromPtr(domainInfo.DomainStatus) + if status == "offline" || status == "checking" || status == "check_failed" || status == "deleting" { + continue + } + + domains = append(domains, lo.FromPtr(domainInfo.DomainName)) + } + } + + if listDomainsResp.Domains == nil || len(*listDomainsResp.Domains) < int(listDomainsPageSize) { + break + } else { + listDomainsPageNumber++ + } + } + + 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, cloudCertId, cloudCertName string) error { // 查询加速域名配置 // REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html showDomainFullConfigReq := &hccdnmodel.ShowDomainFullConfigRequest{ EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), - DomainName: d.config.Domain, + DomainName: domain, } showDomainFullConfigResp, err := d.sdkClient.ShowDomainFullConfig(showDomainFullConfigReq) d.logger.Debug("sdk request 'cdn.ShowDomainFullConfig'", slog.Any("request", showDomainFullConfigReq), slog.Any("response", showDomainFullConfigResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.ShowDomainFullConfig': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.ShowDomainFullConfig': %w", err) } // 更新加速域名配置 // REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html // REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html updateDomainMultiCertificatesReqBodyContent := &hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent{} - updateDomainMultiCertificatesReqBodyContent.DomainName = d.config.Domain + updateDomainMultiCertificatesReqBodyContent.DomainName = domain updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1 updateDomainMultiCertificatesReqBodyContent.CertificateType = lo.ToPtr(int32(2)) - updateDomainMultiCertificatesReqBodyContent.ScmCertificateId = lo.ToPtr(upres.CertId) - updateDomainMultiCertificatesReqBodyContent.CertName = lo.ToPtr(upres.CertName) + updateDomainMultiCertificatesReqBodyContent.ScmCertificateId = lo.ToPtr(cloudCertId) + updateDomainMultiCertificatesReqBodyContent.CertName = lo.ToPtr(cloudCertName) updateDomainMultiCertificatesReqBodyContent = assign(updateDomainMultiCertificatesReqBodyContent, showDomainFullConfigResp.Configs) updateDomainMultiCertificatesReq := &hccdnmodel.UpdateDomainMultiCertificatesRequest{ EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), @@ -124,10 +262,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke updateDomainMultiCertificatesResp, err := d.sdkClient.UpdateDomainMultiCertificates(updateDomainMultiCertificatesReq) d.logger.Debug("sdk request 'cdn.UpdateDomainMultiCertificates'", slog.Any("request", updateDomainMultiCertificatesReq), slog.Any("response", updateDomainMultiCertificatesResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.UpdateDomainMultiCertificates': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.UpdateDomainMultiCertificates': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, secretAccessKey, region string) (*internal.CdnClient, error) { diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go index fa4191bed..236db7e45 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal/client.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal/client.go index f9f5f0406..fc8366f9b 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal/client.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/internal/client.go @@ -16,6 +16,16 @@ func NewCdnClient(hcClient *httpclient.HcHttpClient) *CdnClient { return &CdnClient{HcClient: hcClient} } +func (c *CdnClient) ListDomains(request *model.ListDomainsRequest) (*model.ListDomainsResponse, error) { + requestDef := hwcdn.GenReqDefForListDomains() + + if resp, err := c.HcClient.Sync(request, requestDef); err != nil { + return nil, err + } else { + return resp.(*model.ListDomainsResponse), nil + } +} + func (c *CdnClient) ShowDomainFullConfig(request *model.ShowDomainFullConfigRequest) (*model.ShowDomainFullConfigResponse, error) { requestDef := hwcdn.GenReqDefForShowDomainFullConfig() diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go index 82d90eb2f..6a8eebb4a 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go @@ -79,9 +79,9 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, fmt.Errorf("failed to execute sdk request 'vod.ListDomains': %w", err) } - for _, domain := range listDomainsResp.Result.Content { - if domain.Name == d.config.Domain { - domainId, _ = strconv.Atoi(domain.Id) + for _, domainInfo := range listDomainsResp.Result.Content { + if domainInfo.Name == d.config.Domain { + domainId, _ = strconv.Atoi(domainInfo.Id) break } } 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 3e6fee8eb..bc92ca0dd 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -190,9 +190,9 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w } if describeDomainsResp.Response.Domains != nil { - for _, domain := range describeDomainsResp.Response.Domains { - if lo.FromPtr(domain.Product) == "cdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domain.Domain)) { - domains = append(domains, *domain.Domain) + for _, domainInfo := range describeDomainsResp.Response.Domains { + if lo.FromPtr(domainInfo.Product) == "cdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domainInfo.Domain)) { + domains = append(domains, *domainInfo.Domain) } } } 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 8e53534bf..2155132b2 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -121,12 +121,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: @@ -190,9 +190,9 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w } if describeDomainsResp.Response.Domains != nil { - for _, domain := range describeDomainsResp.Response.Domains { - if lo.FromPtr(domain.Product) == "ecdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domain.Domain)) { - domains = append(domains, *domain.Domain) + for _, domainInfo := range describeDomainsResp.Response.Domains { + if lo.FromPtr(domainInfo.Product) == "ecdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domainInfo.Domain)) { + domains = append(domains, *domainInfo.Domain) } } } 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 1b154f376..6c39e4dcf 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -83,7 +83,7 @@ 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) switch d.config.DomainMatchPattern { case "", DOMAIN_MATCH_PATTERN_EXACT: @@ -92,7 +92,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: @@ -102,14 +102,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} } } @@ -180,9 +180,9 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w } if listCdnDomainsResp.Data != nil { - for _, domain := range listCdnDomainsResp.Data { - if xcerthostname.IsMatch(wildcardDomain, ve.StringValue(domain.Domain)) { - domains = append(domains, ve.StringValue(domain.Domain)) + for _, domainInfo := range listCdnDomainsResp.Data { + if xcerthostname.IsMatch(wildcardDomain, ve.StringValue(domainInfo.Domain)) { + domains = append(domains, ve.StringValue(domainInfo.Domain)) } } } diff --git a/pkg/core/ssl-deployer/providers/volcengine-dcdn/consts.go b/pkg/core/ssl-deployer/providers/volcengine-dcdn/consts.go new file mode 100644 index 000000000..11466778d --- /dev/null +++ b/pkg/core/ssl-deployer/providers/volcengine-dcdn/consts.go @@ -0,0 +1,6 @@ +package volcenginedcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go index 9742e19de..c202f6785 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go @@ -23,6 +23,9 @@ type SSLDeployerProviderConfig struct { AccessKeySecret string `json:"accessKeySecret"` // 火山引擎地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -74,10 +77,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -86,15 +85,30 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // "*.example.com" → ".example.com",适配火山引擎 DCDN 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 获取待部署的域名列表 + domains := make([]string, 0) + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } - // 绑定证书 + // "*.example.com" → ".example.com",适配火山引擎 DCDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{domain} + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 批量绑定证书 // REF: https://www.volcengine.com/docs/6559/1250189 createCertBindReq := &vedcdn.CreateCertBindInput{ CertSource: ve.String("volc"), CertId: ve.String(upres.CertId), - DomainNames: ve.StringSlice([]string{domain}), + DomainNames: ve.StringSlice(domains), } createCertBindResp, err := d.sdkClient.CreateCertBind(createCertBindReq) d.logger.Debug("sdk request 'dcdn.CreateCertBind'", slog.Any("request", createCertBindReq), slog.Any("response", createCertBindResp)) diff --git a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn_test.go b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn_test.go index 86270ff23..5dbc8db6e 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn_test.go +++ b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) 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 e39675d3b..282daddb4 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -193,8 +193,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } if listDomainDetailResp.Result.DomainList != nil { - for _, domain := range listDomainDetailResp.Result.DomainList { - domains = append(domains, domain.Domain) + for _, domainInfo := range listDomainDetailResp.Result.DomainList { + domains = append(domains, domainInfo.Domain) } } diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaiduCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaiduCloudCDN.tsx index 978864347..3a4078736 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaiduCloudCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaiduCloudCDN.tsx @@ -75,7 +75,6 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z .object({ - region: z.string().nullish(), domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), domain: z.string().nullish(), }) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderHuaweiCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderHuaweiCloudCDN.tsx index 0774f221c..fd27e5673 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderHuaweiCloudCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderHuaweiCloudCDN.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 BizDeployNodeConfigFieldsProviderHuaweiCloudCDN = () => { const { i18n, t } = useTranslation(); @@ -15,8 +20,11 @@ const BizDeployNodeConfigFieldsProviderHuaweiCloudCDN = () => { [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 ( <> { + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -44,6 +76,7 @@ const BizDeployNodeConfigFieldsProviderHuaweiCloudCDN = () => { const getInitialValues = (): Nullish>> => { return { region: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -51,10 +84,30 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - region: z.string().nonempty(t("workflow_node.deploy.form.huaweicloud_cdn_region.placeholder")), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.huaweicloud_cdn_region.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderHuaweiCloudCDN, { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineDCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineDCDN.tsx index ef9df492d..ad4c1a0d4 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineDCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderVolcEngineDCDN.tsx @@ -1,5 +1,5 @@ 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"; @@ -7,6 +7,8 @@ import { validDomainName } from "@/utils/validators"; import { useFormNestedFieldsContext } from "./_context"; +const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const; + const BizDeployNodeConfigFieldsProviderVolcEngineDCDN = () => { const { i18n, t } = useTranslation(); @@ -15,10 +17,35 @@ const BizDeployNodeConfigFieldsProviderVolcEngineDCDN = () => { [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 ( <> + + ) : ( + void 0 + ) + } + rules={[formRule]} + > + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + { const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +68,28 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderVolcEngineDCDN, { From 517be3032811bbd674c0c24b0b15820669082c5a Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 12 Nov 2025 21:57:54 +0800 Subject: [PATCH 07/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to jdcloud cdn --- .../certdeploy/deployers/sp_jdcloud_cdn.go | 7 +- .../providers/aliyun-cdn/aliyun_cdn.go | 4 - .../providers/aliyun-dcdn/aliyun_dcdn.go | 4 - .../baiducloud-cdn/baiducloud_cdn.go | 4 - .../huaweicloud-cdn/huaweicloud_cdn.go | 4 - .../providers/jdcloud-cdn/consts.go | 10 ++ .../providers/jdcloud-cdn/internal/client.go | 20 +++ .../providers/jdcloud-cdn/jdcloud_cdn.go | 169 ++++++++++++++++-- .../providers/jdcloud-cdn/jdcloud_cdn_test.go | 7 +- .../volcengine-live/volcengine_live.go | 4 - ...ployNodeConfigFieldsProviderJDCloudCDN.tsx | 69 ++++++- 11 files changed, 249 insertions(+), 53 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/jdcloud-cdn/consts.go diff --git a/internal/certdeploy/deployers/sp_jdcloud_cdn.go b/internal/certdeploy/deployers/sp_jdcloud_cdn.go index 871196b6b..97dd9c370 100644 --- a/internal/certdeploy/deployers/sp_jdcloud_cdn.go +++ b/internal/certdeploy/deployers/sp_jdcloud_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := jdcloudcdn.NewSSLDeployerProvider(&jdcloudcdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + 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 291048bc7..64f53e0b9 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -223,10 +223,6 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } } - if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") - } - return domains, nil } diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index 66e8b1149..4716e05e2 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -223,10 +223,6 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } } - if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") - } - return domains, nil } 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 362a18dfd..d317ee866 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go @@ -176,10 +176,6 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } } - if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") - } - return domains, nil } diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index b6d658da8..ac556895c 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -223,10 +223,6 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } } - if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") - } - return domains, nil } diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/consts.go new file mode 100644 index 000000000..9d7f0a707 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/consts.go @@ -0,0 +1,10 @@ +package jdcloudcdn + +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/jdcloud-cdn/internal/client.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/internal/client.go index 44d4971da..47ebd9465 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/internal/client.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/internal/client.go @@ -33,6 +33,26 @@ func NewCdnClient(credential *core.Credential) *CdnClient { } } +func (c *CdnClient) GetDomainList(request *cdn.GetDomainListRequest) (*cdn.GetDomainListResponse, error) { + if request == nil { + return nil, errors.New("Request object is nil.") + } + + resp, err := c.Send(request, c.ServiceName) + if err != nil { + return nil, err + } + + jdResp := &cdn.GetDomainListResponse{} + err = json.Unmarshal(resp, jdResp) + if err != nil { + c.Logger.Log(core.LogError, "Unmarshal json failed, resp: %s", string(resp)) + return nil, err + } + + return jdResp, err +} + func (c *CdnClient) QueryDomainConfig(request *cdn.QueryDomainConfigRequest) (*cdn.QueryDomainConfigResponse, error) { if request == nil { return nil, errors.New("Request object is nil.") diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go index 64866b1c0..567cdb171 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go @@ -5,13 +5,17 @@ import ( "errors" "fmt" "log/slog" + "strings" jdcore "github.com/jdcloud-api/jdcloud-sdk-go/core" jdcdn "github.com/jdcloud-api/jdcloud-sdk-go/services/cdn/apis" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/jdcloud-cdn/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/jdcloud-ssl" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -19,6 +23,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 京东云 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -69,20 +76,6 @@ 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, fmt.Errorf("config `domain` is required") - } - - // 查询域名配置信息 - // REF: https://docs.jdcloud.com/cn/cdn/api/querydomainconfig - queryDomainConfigReq := jdcdn.NewQueryDomainConfigRequestWithoutParam() - queryDomainConfigReq.SetDomain(d.config.Domain) - queryDomainConfigResp, err := d.sdkClient.QueryDomainConfig(queryDomainConfigReq) - d.logger.Debug("sdk request 'cdn.QueryDomainConfig'", slog.Any("request", queryDomainConfigReq), slog.Any("response", queryDomainConfigResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainConfig': %w", err) - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -91,23 +84,161 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + 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, upres.CertId); 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://docs.jdcloud.com/cn/cdn/api/getdomainlist + getDomainListPageNumber := 1 + getDomainListPageSize := 50 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + // 查询域名配置信息 + // REF: https://docs.jdcloud.com/cn/cdn/api/querydomainconfig + getDomainListReq := jdcdn.NewGetDomainListRequestWithoutParam() + getDomainListReq.SetPageNumber(getDomainListPageNumber) + getDomainListReq.SetPageSize(getDomainListPageSize) + getDomainListResp, err := d.sdkClient.GetDomainList(getDomainListReq) + d.logger.Debug("sdk request 'cdn.GetDomainList'", slog.Any("request", getDomainListReq), slog.Any("response", getDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainList': %w", err) + } + + for _, domainInfo := range getDomainListResp.Result.Domains { + status := domainInfo.Status + if status == "offline" { + continue + } + + domains = append(domains, domainInfo.Domain) + } + + if len(getDomainListResp.Result.Domains) < getDomainListPageSize { + break + } else { + getDomainListPageNumber++ + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 查询域名配置信息 + // REF: https://docs.jdcloud.com/cn/cdn/api/querydomainconfig + queryDomainConfigReq := jdcdn.NewQueryDomainConfigRequestWithoutParam() + queryDomainConfigReq.SetDomain(d.config.Domain) + queryDomainConfigResp, err := d.sdkClient.QueryDomainConfig(queryDomainConfigReq) + d.logger.Debug("sdk request 'cdn.QueryDomainConfig'", slog.Any("request", queryDomainConfigReq), slog.Any("response", queryDomainConfigResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainConfig': %w", err) + } + // 设置通讯协议 // REF: https://docs.jdcloud.com/cn/cdn/api/sethttptype setHttpTypeReq := jdcdn.NewSetHttpTypeRequestWithoutParam() setHttpTypeReq.SetDomain(d.config.Domain) setHttpTypeReq.SetHttpType("https") - setHttpTypeReq.SetCertificate(certPEM) - setHttpTypeReq.SetRsaKey(privkeyPEM) setHttpTypeReq.SetCertFrom("ssl") - setHttpTypeReq.SetSslCertId(upres.CertId) + setHttpTypeReq.SetSslCertId(cloudCertId) setHttpTypeReq.SetJumpType(queryDomainConfigResp.Result.HttpsJumpType) setHttpTypeResp, err := d.sdkClient.SetHttpType(setHttpTypeReq) d.logger.Debug("sdk request 'cdn.QueryDomainConfig'", slog.Any("request", setHttpTypeReq), slog.Any("response", setHttpTypeResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.SetHttpType': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.SetHttpType': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret string) (*internal.CdnClient, error) { diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn_test.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn_test.go index 2ecd406b9..af85d5e05 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) 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 282daddb4..98b4cdab5 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -205,10 +205,6 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } } - 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/BizDeployNodeConfigFieldsProviderJDCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudCDN.tsx index d27c5f077..f027b50ab 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudCDN.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 BizDeployNodeConfigFieldsProviderJDCloudCDN = () => { const { i18n, t } = useTranslation(); @@ -15,24 +20,52 @@ const BizDeployNodeConfigFieldsProviderJDCloudCDN = () => { [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 ( <> + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +73,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderJDCloudCDN, { From 74d68fa6870574f7b8ebd56ea9816dbbbb884cd3 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 12 Nov 2025 21:57:54 +0800 Subject: [PATCH 08/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to byteplus cdn --- .../certdeploy/deployers/sp_byteplus_cdn.go | 7 +- .../providers/byteplus-cdn/byteplus_cdn.go | 174 ++++++++++++++---- .../providers/byteplus-cdn/consts.go | 10 + .../providers/ksyun-cdn/ksyun_cdn.go | 2 +- .../volcengine-cdn/volcengine_cdn.go | 8 +- ...loyNodeConfigFieldsProviderBytePlusCDN.tsx | 69 ++++++- .../i18n/locales/zh/nls.workflow.nodes.json | 4 +- 7 files changed, 221 insertions(+), 53 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/byteplus-cdn/consts.go diff --git a/internal/certdeploy/deployers/sp_byteplus_cdn.go b/internal/certdeploy/deployers/sp_byteplus_cdn.go index d3c814e2f..13d491a60 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 b2c4dd5df..bc9eabcb9 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 000000000..81c2dab50 --- /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 ead9dbc54..a09b800d6 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 6c39e4dcf..936458a1b 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 b5b125fbe..c81d79e44 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 ( <> + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +73,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderBytePlusCDN, { diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 152bbd418..411543a66 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -807,7 +807,7 @@ "workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder": "请输入腾讯云 EdgeOne 站点 ID", "workflow_node.deploy.form.tencentcloud_eo_zone_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", "workflow_node.deploy.form.tencentcloud_eo_domains.label": "腾讯云 EdgeOne 加速域名", - "workflow_node.deploy.form.tencentcloud_eo_domains.placeholder": "请输入腾讯云 EdgeOne 加速域名(支持泛域名;多个值请用半角分号隔开)", + "workflow_node.deploy.form.tencentcloud_eo_domains.placeholder": "请输入腾讯云 EdgeOne 加速域名(多个值请用半角分号隔开)", "workflow_node.deploy.form.tencentcloud_eo_domains.help": "提示:多个域名请用半角分号隔开。", "workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.title": "修改腾讯云 EdgeOne 加速域名", "workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.placeholder": "请输入腾讯云 EdgeOne 加速域名", @@ -973,7 +973,7 @@ "workflow_node.deploy.form.volcengine_tos_domain.label": "火山引擎 TOS 自定义域名", "workflow_node.deploy.form.volcengine_tos_domain.placeholder": "请输入火山引擎 TOS 自定义域名", "workflow_node.deploy.form.wangsu_cdn_domains.label": "网宿云 CDN 加速域名", - "workflow_node.deploy.form.wangsu_cdn_domains.placeholder": "请输入网宿云 CDN 加速域名(支持泛域名;多个值请用半角分号隔开)", + "workflow_node.deploy.form.wangsu_cdn_domains.placeholder": "请输入网宿云 CDN 加速域名(多个值请用半角分号隔开)", "workflow_node.deploy.form.wangsu_cdn_domains.help": "提示:多个域名请用半角分号隔开。", "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.title": "修改网宿云 CDN 加速域名", "workflow_node.deploy.form.wangsu_cdn_domains.multiple_input_modal.placeholder": "请输入网宿云 CDN 加速域名", From f371d74522c24ff1c5113607e26bf407e9c8e7e2 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:02 +0800 Subject: [PATCH 09/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to ctyun ao --- .../certdeploy/deployers/sp_ctcccloud_ao.go | 7 +- internal/certdeploy/deployers/sp_ksyun_cdn.go | 9 +- .../providers/aliyun-cdn/aliyun_cdn.go | 19 +- .../providers/aliyun-dcdn/aliyun_dcdn.go | 8 +- .../baiducloud-cdn/baiducloud_cdn.go | 4 +- .../providers/byteplus-cdn/byteplus_cdn.go | 2 +- .../providers/ctcccloud-ao/consts.go | 10 + .../providers/ctcccloud-ao/ctcccloud_ao.go | 154 +++++++++++- .../ctcccloud-ao/ctcccloud_ao_test.go | 7 +- .../huaweicloud-cdn/huaweicloud_cdn.go | 8 +- .../providers/jdcloud-cdn/jdcloud_cdn.go | 8 +- .../providers/ksyun-cdn/consts.go | 6 + .../providers/ksyun-cdn/ksyun_cdn.go | 223 ++++++++++-------- .../providers/ksyun-cdn/ksyun_cdn_test.go | 9 +- .../tencentcloud-eo/tencentcloud_eo.go | 4 +- .../volcengine-cdn/volcengine_cdn.go | 4 +- .../volcengine-live/volcengine_live.go | 4 +- pkg/sdk3rd/ctyun/ao/api_query_domains.go | 67 ++++++ pkg/sdk3rd/ctyun/ao/types.go | 10 + ...loyNodeConfigFieldsProviderCTCCCloudAO.tsx | 69 +++++- ...DeployNodeConfigFieldsProviderKsyunCDN.tsx | 57 ++++- .../i18n/locales/en/nls.workflow.nodes.json | 2 +- .../i18n/locales/zh/nls.workflow.nodes.json | 2 +- 23 files changed, 528 insertions(+), 165 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/ctcccloud-ao/consts.go create mode 100644 pkg/core/ssl-deployer/providers/ksyun-cdn/consts.go create mode 100644 pkg/sdk3rd/ctyun/ao/api_query_domains.go diff --git a/internal/certdeploy/deployers/sp_ctcccloud_ao.go b/internal/certdeploy/deployers/sp_ctcccloud_ao.go index f28dcbce4..01f6658db 100644 --- a/internal/certdeploy/deployers/sp_ctcccloud_ao.go +++ b/internal/certdeploy/deployers/sp_ctcccloud_ao.go @@ -17,9 +17,10 @@ func init() { } provider, err := ctcccloudao.NewSSLDeployerProvider(&ctcccloudao.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/internal/certdeploy/deployers/sp_ksyun_cdn.go b/internal/certdeploy/deployers/sp_ksyun_cdn.go index 2aafdbfe6..40866f2b7 100644 --- a/internal/certdeploy/deployers/sp_ksyun_cdn.go +++ b/internal/certdeploy/deployers/sp_ksyun_cdn.go @@ -17,10 +17,11 @@ func init() { } provider, err := ksyuncdn.NewSSLDeployerProvider(&ksyuncdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - SecretAccessKey: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), }) 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 64f53e0b9..aeb63919f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -123,7 +123,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return xcerthostname.IsMatch(d.config.Domain, domain) }) if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") + return nil, errors.New("could not find any domains matched by wildcard") } } else { domains = []string{d.config.Domain} @@ -146,7 +146,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return certX509.VerifyHostname(domain) == nil }) if len(domains) == 0 { - return nil, errors.New("no domains matched by certificate") + return nil, errors.New("could not find any domains matched by certificate") } } @@ -197,7 +197,6 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro describeUserDomainsReq := &alicdn.DescribeUserDomainsRequest{ ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), - CheckDomainShow: tea.Bool(true), PageNumber: tea.Int32(describeUserDomainsPageNumber), PageSize: tea.Int32(describeUserDomainsPageSize), } @@ -207,13 +206,15 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeUserDomains': %w", err) } - for _, domain := range describeUserDomainsResp.Body.Domains.PageData { - status := tea.StringValue(domain.DomainStatus) - if status == "stopping" || status == "deleting" { - continue - } + if describeUserDomainsResp.Body.Domains != nil { + ignoredStatuses := []string{"offline", "checking", "check_failed", "stopping", "deleting"} + for _, domainInfo := range describeUserDomainsResp.Body.Domains.PageData { + if lo.Contains(ignoredStatuses, tea.StringValue(domainInfo.DomainStatus)) { + continue + } - domains = append(domains, tea.StringValue(domain.DomainName)) + domains = append(domains, tea.StringValue(domainInfo.DomainName)) + } } if len(describeUserDomainsResp.Body.Domains.PageData) < int(describeUserDomainsPageNumber) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index 4716e05e2..58c195d15 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -123,7 +123,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return xcerthostname.IsMatch(d.config.Domain, domain) }) if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") + return nil, errors.New("could not find any domains matched by wildcard") } } else { domains = []string{d.config.Domain} @@ -146,7 +146,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return certX509.VerifyHostname(domain) == nil }) if len(domains) == 0 { - return nil, errors.New("no domains matched by certificate") + return nil, errors.New("could not find any domains matched by certificate") } } @@ -207,9 +207,9 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'dcdn.DescribeDcdnUserDomains': %w", err) } + ignoredStatuses := []string{"offline", "checking", "check_failed", "stopping", "deleting"} for _, domainInfo := range describeDcdnUserDomainsResp.Body.Domains.PageData { - status := tea.StringValue(domainInfo.DomainStatus) - if status == "stopping" || status == "deleting" { + if lo.Contains(ignoredStatuses, tea.StringValue(domainInfo.DomainStatus)) { continue } 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 d317ee866..f360e2e77 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go @@ -91,7 +91,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return xcerthostname.IsMatch(d.config.Domain, domain) }) if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") + return nil, errors.New("could not find any domains matched by wildcard") } } else { domains = []string{d.config.Domain} @@ -114,7 +114,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return certX509.VerifyHostname(domain) == nil }) if len(domains) == 0 { - return nil, errors.New("no domains matched by certificate") + return nil, errors.New("could not find any domains matched by certificate") } } 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 bc9eabcb9..8a7180a21 100644 --- a/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go +++ b/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -220,7 +220,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, clo if len(domains) == 0 { if len(describeCertConfigResp.Result.SpecifiedCertConfig) == 0 { - return nil, errors.New("no domains matched by certificate") + return nil, errors.New("could not find any domains matched by certificate") } } diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/consts.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/consts.go new file mode 100644 index 000000000..4ad94ec53 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/consts.go @@ -0,0 +1,10 @@ +package ctcccloudao + +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/ctcccloud-ao/ctcccloud_ao.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go index 6dd6c8107..1a236a1b7 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go @@ -6,12 +6,15 @@ import ( "fmt" "log/slog" "strconv" + "strings" "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/ctcccloud-ao" ctyunao "github.com/certimate-go/certimate/pkg/sdk3rd/ctyun/ao" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -19,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"` } @@ -67,10 +73,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -79,22 +81,154 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + 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("could not find any 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("could not find any 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 accessone domains to deploy") + } else { + d.logger.Info("found accessone 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, upres.CertId, upres.CertName); 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://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13816&data=174&isNormal=1&vid=167 + queryDomainsPage := int32(1) + queryDomainsPageSize := int32(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listDomainsReq := &ctyunao.QueryDomainsRequest{ + Page: lo.ToPtr(queryDomainsPage), + PageSize: lo.ToPtr(queryDomainsPageSize), + ProductCode: lo.ToPtr("020"), + } + listDomainsResp, err := d.sdkClient.QueryDomains(listDomainsReq) + d.logger.Debug("sdk request 'cdn.QueryDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomains': %w", err) + } + + if listDomainsResp.ReturnObj != nil { + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainInfo := range listDomainsResp.ReturnObj.Results { + if lo.Contains(ignoredStatuses, domainInfo.Status) { + continue + } + + domains = append(domains, domainInfo.Domain) + } + } + + if listDomainsResp.ReturnObj == nil || len(listDomainsResp.ReturnObj.Results) < int(queryDomainsPageSize) { + break + } else { + queryDomainsPage++ + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId, cloudCertName string) error { // 域名基础及加速配置查询 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13412&data=174&isNormal=1&vid=167 getDomainConfigReq := &ctyunao.GetDomainConfigRequest{ - Domain: lo.ToPtr(d.config.Domain), + Domain: lo.ToPtr(domain), ProductCode: lo.ToPtr("020"), } getDomainConfigResp, err := d.sdkClient.GetDomainConfig(getDomainConfigReq) d.logger.Debug("sdk request 'cdn.GetDomainConfig'", slog.Any("request", getDomainConfigReq), slog.Any("response", getDomainConfigResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainConfig': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.GetDomainConfig': %w", err) } // 域名基础及加速配置修改 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13413&data=174&isNormal=1&vid=167 modifyDomainConfigReq := &ctyunao.ModifyDomainConfigRequest{ - Domain: lo.ToPtr(d.config.Domain), + Domain: lo.ToPtr(domain), ProductCode: lo.ToPtr(getDomainConfigResp.ReturnObj.ProductCode), Origin: lo.Map(getDomainConfigResp.ReturnObj.Origin, func(item *ctyunao.DomainOriginConfigWithWeight, _ int) *ctyunao.DomainOriginConfig { weight := item.Weight @@ -108,15 +242,15 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } }), HttpsStatus: lo.ToPtr("on"), - CertName: lo.ToPtr(upres.CertName), + CertName: lo.ToPtr(cloudCertName), } modifyDomainConfigResp, err := d.sdkClient.ModifyDomainConfig(modifyDomainConfigReq) d.logger.Debug("sdk request 'cdn.ModifyDomainConfig'", slog.Any("request", modifyDomainConfigReq), slog.Any("response", modifyDomainConfigResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainConfig': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainConfig': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunao.Client, error) { diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao_test.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao_test.go index 3fc1413f8..7f0b0f4cb 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao_test.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao_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/huaweicloud-cdn/huaweicloud_cdn.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index ac556895c..63e990fbb 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -123,7 +123,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return xcerthostname.IsMatch(d.config.Domain, domain) }) if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") + return nil, errors.New("could not find any domains matched by wildcard") } } else { domains = []string{d.config.Domain} @@ -146,7 +146,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return certX509.VerifyHostname(domain) == nil }) if len(domains) == 0 { - return nil, errors.New("no domains matched by certificate") + return nil, errors.New("could not find any domains matched by certificate") } } @@ -206,9 +206,9 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } if listDomainsResp.Domains != nil { + ignoredStatuses := []string{"offline", "checking", "check_failed", "deleting"} for _, domainInfo := range *listDomainsResp.Domains { - status := lo.FromPtr(domainInfo.DomainStatus) - if status == "offline" || status == "checking" || status == "check_failed" || status == "deleting" { + if lo.Contains(ignoredStatuses, lo.FromPtr(domainInfo.DomainStatus)) { continue } diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go index 567cdb171..147fb7f22 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go @@ -112,7 +112,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return xcerthostname.IsMatch(d.config.Domain, domain) }) if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") + return nil, errors.New("could not find any domains matched by wildcard") } } else { domains = []string{d.config.Domain} @@ -135,7 +135,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return certX509.VerifyHostname(domain) == nil }) if len(domains) == 0 { - return nil, errors.New("no domains matched by certificate") + return nil, errors.New("could not find any domains matched by certificate") } } @@ -194,9 +194,9 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainList': %w", err) } + ignoredStatuses := []string{"offline"} for _, domainInfo := range getDomainListResp.Result.Domains { - status := domainInfo.Status - if status == "offline" { + if lo.Contains(ignoredStatuses, domainInfo.Status) { continue } diff --git a/pkg/core/ssl-deployer/providers/ksyun-cdn/consts.go b/pkg/core/ssl-deployer/providers/ksyun-cdn/consts.go new file mode 100644 index 000000000..950343558 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ksyun-cdn/consts.go @@ -0,0 +1,6 @@ +package ksyuncdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) 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 a09b800d6..956bfa0d1 100644 --- a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go @@ -20,6 +20,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"` // 证书 ID。 @@ -63,110 +66,138 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { // 如果原证书 ID 为空,则创建证书;否则更新证书。 if d.config.CertificateId == "" { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") - } - - // 遍历查询域名列表,获取域名 ID - // https://docs.ksyun.com/documents/198 - var domainId string - getCdnDomainsPageNumber := int32(1) - getCdnDomainsPageSize := int32(100) - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - getCdnDomainsInput := map[string]any{ - "PageNumber": getCdnDomainsPageNumber, - "PageSize": getCdnDomainsPageSize, - "DomainName": d.config.Domain, - "FuzzyMatch": "off", - } - getCdnDomainsReq, getCdnDomainsOutput := d.sdkClient.GetCdnDomainsPostRequest(&getCdnDomainsInput) - getCdnDomainsErr := getCdnDomainsReq.Send() - d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput)) - if getCdnDomainsErr != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", getCdnDomainsErr) - } - - type GetCdnDomainsResponse struct { - PageNumber int32 `json:"PageNumber"` - PageSize int32 `json:"PageSize"` - TotalCount int32 `json:"TotalCount"` - Domains []*struct { - DomainId string `json:"DomainId"` - DomainName string `json:"DomainName"` - Cname string `json:"Cname"` - CdnType string `json:"CdnType"` - CreatedTime string `json:"CreatedTime"` - ModifiedTime string `json:"ModifiedTime"` - Region string `json:"Region"` - } `json:"Domains"` - } - var getCdnDomainsResp *GetCdnDomainsResponse - mapstructure.Decode(getCdnDomainsOutput, &getCdnDomainsResp) - - if getCdnDomainsResp != nil { - for _, domainItem := range getCdnDomainsResp.Domains { - if strings.EqualFold(domainItem.DomainName, d.config.Domain) { - domainId = domainItem.DomainId - break - } - } - - if domainId != "" { - break - } - } - - if getCdnDomainsResp == nil || len(getCdnDomainsResp.Domains) < int(getCdnDomainsPageSize) { - break - } else { - getCdnDomainsPageNumber++ - } - } - if domainId == "" { - return nil, errors.New("domain not found") - } - - // 为加速域名配置证书接口 - // https://docs.ksyun.com/documents/261 - configCertificateInput := map[string]any{ - "Enable": "on", - "DomainIds": domainId, - "CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()), - "ServerCertificate": certPEM, - "PrivateKey": privkeyPEM, - } - configCertificateReq, configCertificateOutput := d.sdkClient.ConfigCertificatePostRequest(&configCertificateInput) - configCertificateErr := configCertificateReq.Send() - d.logger.Debug("sdk request 'cdn.ConfigCertificate'", slog.Any("request", configCertificateInput), slog.Any("response", configCertificateOutput)) - if configCertificateErr != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.ConfigCertificate': %w", configCertificateErr) + if err := d.deployToDomain(ctx, certPEM, privkeyPEM); err != nil { + return nil, err } } else { - // 更新证书 - // https://docs.ksyun.com/documents/259 - setCertificateInput := map[string]any{ - "CertificateId": d.config.CertificateId, - "CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()), - "ServerCertificate": certPEM, - "PrivateKey": privkeyPEM, - } - setCertificateReq, setCertificateOutput := d.sdkClient.SetCertificatePostRequest(&setCertificateInput) - setCertificateErr := setCertificateReq.Send() - d.logger.Debug("sdk request 'cdn.SetCertificate'", slog.Any("request", setCertificateInput), slog.Any("response", setCertificateOutput)) - if setCertificateErr != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.SetCertificate': %w", setCertificateErr) + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err } } return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) deployToDomain(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + // 遍历查询域名列表,获取域名 ID + // https://docs.ksyun.com/documents/198 + var domainId string + getCdnDomainsPageNumber := int32(1) + getCdnDomainsPageSize := int32(100) + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + getCdnDomainsInput := map[string]any{ + "PageNumber": getCdnDomainsPageNumber, + "PageSize": getCdnDomainsPageSize, + "DomainName": d.config.Domain, + "FuzzyMatch": "off", + } + getCdnDomainsReq, getCdnDomainsOutput := d.sdkClient.GetCdnDomainsPostRequest(&getCdnDomainsInput) + getCdnDomainsErr := getCdnDomainsReq.Send() + d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput)) + if getCdnDomainsErr != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", getCdnDomainsErr) + } + + type GetCdnDomainsResponse struct { + PageNumber int32 `json:"PageNumber"` + PageSize int32 `json:"PageSize"` + TotalCount int32 `json:"TotalCount"` + Domains []*struct { + DomainId string `json:"DomainId"` + DomainName string `json:"DomainName"` + Cname string `json:"Cname"` + CdnType string `json:"CdnType"` + CreatedTime string `json:"CreatedTime"` + ModifiedTime string `json:"ModifiedTime"` + Region string `json:"Region"` + } `json:"Domains"` + } + var getCdnDomainsResp *GetCdnDomainsResponse + mapstructure.Decode(getCdnDomainsOutput, &getCdnDomainsResp) + + if getCdnDomainsResp != nil { + for _, domainItem := range getCdnDomainsResp.Domains { + if strings.EqualFold(domainItem.DomainName, d.config.Domain) { + domainId = domainItem.DomainId + break + } + } + + if domainId != "" { + break + } + } + + if getCdnDomainsResp == nil || len(getCdnDomainsResp.Domains) < int(getCdnDomainsPageSize) { + break + } else { + getCdnDomainsPageNumber++ + } + } + if domainId == "" { + return errors.New("domain not found") + } + + if err := d.updateDomainCertificate(ctx, domainId, certPEM, privkeyPEM); err != nil { + return err + } + + return nil +} + +func (d *SSLDeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.CertificateId == "" { + return errors.New("config `certificateId` is required") + } + + // 更新证书 + // https://docs.ksyun.com/documents/259 + setCertificateInput := map[string]any{ + "CertificateId": d.config.CertificateId, + "CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()), + "ServerCertificate": certPEM, + "PrivateKey": privkeyPEM, + } + setCertificateReq, setCertificateOutput := d.sdkClient.SetCertificatePostRequest(&setCertificateInput) + setCertificateErr := setCertificateReq.Send() + d.logger.Debug("sdk request 'cdn.SetCertificate'", slog.Any("request", setCertificateInput), slog.Any("response", setCertificateOutput)) + if setCertificateErr != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.SetCertificate': %w", setCertificateErr) + } + + return nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domainId string, certPEM string, privkeyPEM string) error { + // 为加速域名配置证书接口 + // https://docs.ksyun.com/documents/261 + configCertificateInput := map[string]any{ + "Enable": "on", + "DomainIds": domainId, + "CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()), + "ServerCertificate": certPEM, + "PrivateKey": privkeyPEM, + } + configCertificateReq, configCertificateOutput := d.sdkClient.ConfigCertificatePostRequest(&configCertificateInput) + configCertificateErr := configCertificateReq.Send() + d.logger.Debug("sdk request 'cdn.ConfigCertificate'", slog.Any("request", configCertificateInput), slog.Any("response", configCertificateOutput)) + if configCertificateErr != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.ConfigCertificate': %w", configCertificateErr) + } + + return nil +} + func createSDKClient(accessKeyId, secretAccessKey string) (*ksccdnv1.Cdnv1, error) { region := "cn-beijing-6" client := ksccdnv1.SdkNew(ksc.NewClient(accessKeyId, secretAccessKey), &ksc.Config{Region: ®ion}) diff --git a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn_test.go b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn_test.go index e5fdb5f8c..5c51bc079 100644 --- a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Domain: fDomain, - CertificateId: fCertificateId, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, + CertificateId: fCertificateId, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go index e0524e3c9..ba4150e24 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go @@ -128,7 +128,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return false }) if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") + return nil, errors.New("could not find any domains matched by wildcard") } } @@ -148,7 +148,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return certX509.VerifyHostname(domain) == nil }) if len(domains) == 0 { - return nil, errors.New("no domains matched by certificate") + return nil, errors.New("could not find any domains matched by certificate") } } 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 936458a1b..8e30e2857 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -195,7 +195,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w } if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") + return nil, errors.New("could not find any domains matched by wildcard") } return domains, nil @@ -229,7 +229,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, clo if len(domains) == 0 { if len(describeCertConfigResp.SpecifiedCertConfig) == 0 { - return nil, errors.New("no domains matched by certificate") + return nil, errors.New("could not find any domains matched by certificate") } } 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 98b4cdab5..c1054793f 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -110,7 +110,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return xcerthostname.IsMatch(d.config.Domain, domain) }) if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") + return nil, errors.New("could not find any domains matched by wildcard") } } else { domains = append(domains, d.config.Domain) @@ -133,7 +133,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return certX509.VerifyHostname(domain) == nil }) if len(domains) == 0 { - return nil, errors.New("no domains matched by certificate") + return nil, errors.New("could not find any domains matched by certificate") } } diff --git a/pkg/sdk3rd/ctyun/ao/api_query_domains.go b/pkg/sdk3rd/ctyun/ao/api_query_domains.go new file mode 100644 index 000000000..96f947df5 --- /dev/null +++ b/pkg/sdk3rd/ctyun/ao/api_query_domains.go @@ -0,0 +1,67 @@ +package ao + +import ( + "context" + "net/http" + "strconv" +) + +type QueryDomainsRequest struct { + Page *int32 `json:"page,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + Domain *string `json:"domain,omitempty"` + ProductCode *string `json:"product_code,omitempty"` + Status *int32 `json:"status,omitempty"` + AreaScope *int32 `json:"area_scope,omitempty"` +} + +type QueryDomainsResponse struct { + apiResponseBase + + ReturnObj *struct { + Results []*DomainRecord `json:"result,omitempty"` + Page int32 `json:"page,omitempty"` + PageSize int32 `json:"page_size,omitempty"` + PageCount int32 `json:"page_count,omitempty"` + Total int32 `json:"total,omitempty"` + } `json:"returnObj,omitempty"` +} + +func (c *Client) QueryDomains(req *QueryDomainsRequest) (*QueryDomainsResponse, error) { + return c.QueryDomainsWithContext(context.Background(), req) +} + +func (c *Client) QueryDomainsWithContext(ctx context.Context, req *QueryDomainsRequest) (*QueryDomainsResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/ctapi/v2/domain/query") + if err != nil { + return nil, err + } else { + if req.Page != nil { + httpreq.SetQueryParam("page", strconv.Itoa(int(*req.Page))) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_size", strconv.Itoa(int(*req.PageSize))) + } + if req.Domain != nil { + httpreq.SetQueryParam("domain", *req.Domain) + } + if req.ProductCode != nil { + httpreq.SetQueryParam("product_code", *req.ProductCode) + } + if req.Status != nil { + httpreq.SetQueryParam("status", strconv.Itoa(int(*req.Status))) + } + if req.AreaScope != nil { + httpreq.SetQueryParam("area_scope", strconv.Itoa(int(*req.AreaScope))) + } + + httpreq.SetContext(ctx) + } + + result := &QueryDomainsResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/ctyun/ao/types.go b/pkg/sdk3rd/ctyun/ao/types.go index c22b19e40..309b394a3 100644 --- a/pkg/sdk3rd/ctyun/ao/types.go +++ b/pkg/sdk3rd/ctyun/ao/types.go @@ -89,6 +89,16 @@ type CertDetail struct { Key string `json:"key"` } +type DomainRecord struct { + Domain string `json:"domain"` + CNAME string `json:"cname"` + ProductCode string `json:"product_code"` + ProductName string `json:"product_name"` + Status int32 `json:"status"` + AreaScope int32 `json:"area_scope"` + InsertDate int64 `json:"insert_date"` +} + type DomainOriginConfig struct { Origin string `json:"origin"` Role string `json:"role"` diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudAO.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudAO.tsx index 14cca4ce5..6b97582ae 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudAO.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudAO.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 BizDeployNodeConfigFieldsProviderCTCCCloudAO = () => { const { i18n, t } = useTranslation(); @@ -15,24 +20,52 @@ const BizDeployNodeConfigFieldsProviderCTCCCloudAO = () => { [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 ( <> + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +73,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderCTCCCloudAO, { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKsyunCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKsyunCDN.tsx index 1c0bc23b8..4c0335846 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKsyunCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKsyunCDN.tsx @@ -1,5 +1,5 @@ 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"; @@ -7,6 +7,8 @@ import { validDomainName } from "@/utils/validators"; import { useFormNestedFieldsContext } from "./_context"; +const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const; + const BizDeployNodeConfigFieldsProviderKsyunCDN = () => { const { i18n, t } = useTranslation(); @@ -15,10 +17,35 @@ const BizDeployNodeConfigFieldsProviderKsyunCDN = () => { [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 ( <> + + ) : ( + void 0 + ) + } + rules={[formRule]} + > + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + { const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -51,10 +79,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - certificateId: z.string().nullish(), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + certificateId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderKsyunCDN, { diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 27317d614..6e16cfade 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -198,7 +198,7 @@ "workflow_node.deploy.form.shared_domain_match_pattern.option.exact.label": "Exact matches", "workflow_node.deploy.form.shared_domain_match_pattern.option.wildcard.label": "Wildcard matches", "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "via Certificate", - "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "Notes: For sites that support wildcards, \"Exact matches\" will only apply to the site itself, not include its subdomains.", + "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "Notes: For wildcard resolution sites, \"Exact matches\" only includes the site itself, does not include its subdomains.", "workflow_node.deploy.form.1panel_console_auto_restart.label": "Auto restart 1Panel after deployment", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel node name (Optional)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "Please enter 1Panel node name", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 411543a66..03397829a 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -197,7 +197,7 @@ "workflow_node.deploy.form.shared_domain_match_pattern.option.exact.label": "精确匹配", "workflow_node.deploy.form.shared_domain_match_pattern.option.wildcard.label": "通配符匹配(泛域名)", "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "根据证书自动匹配", - "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "注意:对于支持泛解析的站点,精确匹配仅适用于该泛解析站点本身,而不包括符合条件的子域名。", + "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "注意:对于泛解析的站点,精确匹配仅包含该泛解析站点本身、不包括相关子域名站点。", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启 1Panel 服务", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel 子节点名称(可选)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "请输入 1Panel 子节点名称", From 6243a786262747d3a082dcf29125dba6dd850a07 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:02 +0800 Subject: [PATCH 10/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to ctyun cdn --- .../certdeploy/deployers/sp_ctcccloud_cdn.go | 7 +- .../providers/aliyun-cdn/aliyun_cdn.go | 3 +- .../providers/aliyun-dcdn/aliyun_dcdn.go | 3 +- .../providers/ctcccloud-ao/ctcccloud_ao.go | 12 +- .../providers/ctcccloud-cdn/consts.go | 10 + .../providers/ctcccloud-cdn/ctcccloud_cdn.go | 186 +++++++++++++++--- .../ctcccloud-cdn/ctcccloud_cdn_test.go | 7 +- .../providers/jdcloud-cdn/jdcloud_cdn.go | 2 - pkg/sdk3rd/ctyun/ao/types.go | 4 +- .../ctyun/cdn/api_query_domain_detail.go | 18 +- pkg/sdk3rd/ctyun/cdn/api_query_domain_list.go | 67 +++++++ pkg/sdk3rd/ctyun/cdn/types.go | 26 +++ pkg/utils/cert/hostname/hostname.go | 4 +- ...oyNodeConfigFieldsProviderCTCCCloudCDN.tsx | 69 ++++++- 14 files changed, 349 insertions(+), 69 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/ctcccloud-cdn/consts.go create mode 100644 pkg/sdk3rd/ctyun/cdn/api_query_domain_list.go diff --git a/internal/certdeploy/deployers/sp_ctcccloud_cdn.go b/internal/certdeploy/deployers/sp_ctcccloud_cdn.go index 72d44d392..5d3b7349a 100644 --- a/internal/certdeploy/deployers/sp_ctcccloud_cdn.go +++ b/internal/certdeploy/deployers/sp_ctcccloud_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := ctcccloudcdn.NewSSLDeployerProvider(&ctcccloudcdn.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 aeb63919f..b8bbf1a51 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -120,7 +120,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { - return xcerthostname.IsMatch(d.config.Domain, domain) + return xcerthostname.IsMatch(d.config.Domain, domain) || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") }) if len(domains) == 0 { return nil, errors.New("could not find any domains matched by wildcard") diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index 58c195d15..a1c541736 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -120,7 +120,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { - return xcerthostname.IsMatch(d.config.Domain, domain) + return xcerthostname.IsMatch(d.config.Domain, domain) || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") }) if len(domains) == 0 { return nil, errors.New("could not find any domains matched by wildcard") diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go index 1a236a1b7..da7c71a28 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go @@ -180,20 +180,20 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro default: } - listDomainsReq := &ctyunao.QueryDomainsRequest{ + queryDomainsReq := &ctyunao.QueryDomainsRequest{ Page: lo.ToPtr(queryDomainsPage), PageSize: lo.ToPtr(queryDomainsPageSize), ProductCode: lo.ToPtr("020"), } - listDomainsResp, err := d.sdkClient.QueryDomains(listDomainsReq) - d.logger.Debug("sdk request 'cdn.QueryDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + queryDomainsResp, err := d.sdkClient.QueryDomains(queryDomainsReq) + d.logger.Debug("sdk request 'cdn.QueryDomains'", slog.Any("request", queryDomainsReq), slog.Any("response", queryDomainsResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomains': %w", err) } - if listDomainsResp.ReturnObj != nil { + if queryDomainsResp.ReturnObj != nil { ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} - for _, domainInfo := range listDomainsResp.ReturnObj.Results { + for _, domainInfo := range queryDomainsResp.ReturnObj.Results { if lo.Contains(ignoredStatuses, domainInfo.Status) { continue } @@ -202,7 +202,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } } - if listDomainsResp.ReturnObj == nil || len(listDomainsResp.ReturnObj.Results) < int(queryDomainsPageSize) { + if queryDomainsResp.ReturnObj == nil || len(queryDomainsResp.ReturnObj.Results) < int(queryDomainsPageSize) { break } else { queryDomainsPage++ diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/consts.go new file mode 100644 index 000000000..2999eb8d7 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/consts.go @@ -0,0 +1,10 @@ +package ctcccloudcdn + +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/ctcccloud-cdn/ctcccloud_cdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go index 687c3ec30..f8a20f630 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go @@ -5,12 +5,15 @@ import ( "errors" "fmt" "log/slog" + "strings" "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/ctcccloud-cdn" ctyuncdn "github.com/certimate-go/certimate/pkg/sdk3rd/ctyun/cdn" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -18,6 +21,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"` } @@ -66,10 +72,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -78,33 +80,169 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 查询域名配置信息 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11304&data=161&isNormal=1&vid=154 - queryDomainDetailReq := &ctyuncdn.QueryDomainDetailRequest{ - Domain: lo.ToPtr(d.config.Domain), - } - queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) - d.logger.Debug("sdk request 'cdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainDetail': %w", err) + // 获取待部署的域名列表 + 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("could not find any 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("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 修改域名配置 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11308&data=161&isNormal=1&vid=154 - updateDomainReq := &ctyuncdn.UpdateDomainRequest{ - Domain: lo.ToPtr(d.config.Domain), - HttpsStatus: lo.ToPtr("on"), - CertName: lo.ToPtr(upres.CertName), - } - updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) - d.logger.Debug("sdk request 'cdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.UpdateDomain': %w", err) + // 遍历更新域名证书 + 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, upres.CertId, upres.CertName); 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://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13816&data=174&isNormal=1&vid=167 + queryDomainListPage := int32(1) + queryDomainListPageSize := int32(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + queryDomainListReq := &ctyuncdn.QueryDomainListRequest{ + Page: lo.ToPtr(queryDomainListPage), + PageSize: lo.ToPtr(queryDomainListPageSize), + ProductCode: lo.ToPtr("020"), + } + queryDomainListResp, err := d.sdkClient.QueryDomainList(queryDomainListReq) + d.logger.Debug("sdk request 'cdn.QueryDomainList'", slog.Any("request", queryDomainListReq), slog.Any("response", queryDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainList': %w", err) + } + + if queryDomainListResp.ReturnObj != nil { + filteredProductCodes := []string{"001", "003", "004", "008"} + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainInfo := range queryDomainListResp.ReturnObj.Results { + if !lo.Contains(filteredProductCodes, domainInfo.ProductCode) { + continue + } + if lo.Contains(ignoredStatuses, domainInfo.Status) { + continue + } + + domains = append(domains, domainInfo.Domain) + } + } + + if queryDomainListResp.ReturnObj == nil || len(queryDomainListResp.ReturnObj.Results) < int(queryDomainListPageSize) { + break + } else { + queryDomainListPage++ + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId, cloudCertName string) error { + // 查询域名配置信息 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11304&data=161&isNormal=1&vid=154 + queryDomainDetailReq := &ctyuncdn.QueryDomainDetailRequest{ + Domain: lo.ToPtr(domain), + } + queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) + d.logger.Debug("sdk request 'cdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainDetail': %w", err) + } + + // 修改域名配置 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11308&data=161&isNormal=1&vid=154 + updateDomainReq := &ctyuncdn.UpdateDomainRequest{ + Domain: lo.ToPtr(domain), + HttpsStatus: lo.ToPtr("on"), + CertName: lo.ToPtr(cloudCertName), + } + updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) + d.logger.Debug("sdk request 'cdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.UpdateDomain': %w", err) + } + + return nil +} + func createSDKClient(accessKeyId, secretAccessKey string) (*ctyuncdn.Client, error) { return ctyuncdn.NewClient(accessKeyId, secretAccessKey) } diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn_test.go b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn_test.go index de706e606..41e082f8a 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_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/jdcloud-cdn/jdcloud_cdn.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go index 147fb7f22..bb7a84014 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go @@ -183,8 +183,6 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro default: } - // 查询域名配置信息 - // REF: https://docs.jdcloud.com/cn/cdn/api/querydomainconfig getDomainListReq := jdcdn.NewGetDomainListRequestWithoutParam() getDomainListReq.SetPageNumber(getDomainListPageNumber) getDomainListReq.SetPageSize(getDomainListPageSize) diff --git a/pkg/sdk3rd/ctyun/ao/types.go b/pkg/sdk3rd/ctyun/ao/types.go index 309b394a3..46720c230 100644 --- a/pkg/sdk3rd/ctyun/ao/types.go +++ b/pkg/sdk3rd/ctyun/ao/types.go @@ -91,12 +91,12 @@ type CertDetail struct { type DomainRecord struct { Domain string `json:"domain"` - CNAME string `json:"cname"` + Cname string `json:"cname"` ProductCode string `json:"product_code"` ProductName string `json:"product_name"` Status int32 `json:"status"` AreaScope int32 `json:"area_scope"` - InsertDate int64 `json:"insert_date"` + CreatedTime int64 `json:"insert_date"` } type DomainOriginConfig struct { diff --git a/pkg/sdk3rd/ctyun/cdn/api_query_domain_detail.go b/pkg/sdk3rd/ctyun/cdn/api_query_domain_detail.go index f66cc44ab..b2de12738 100644 --- a/pkg/sdk3rd/ctyun/cdn/api_query_domain_detail.go +++ b/pkg/sdk3rd/ctyun/cdn/api_query_domain_detail.go @@ -14,23 +14,7 @@ type QueryDomainDetailRequest struct { type QueryDomainDetailResponse struct { apiResponseBase - ReturnObj *struct { - Domain string `json:"domain"` - ProductCode string `json:"product_code"` - Status int32 `json:"status"` - AreaScope int32 `json:"area_scope"` - Cname string `json:"cname"` - HttpsStatus string `json:"https_status"` - HttpsBasic *struct { - HttpsForce string `json:"https_force"` - HttpForce string `json:"http_force"` - ForceStatus string `json:"force_status"` - OriginProtocol string `json:"origin_protocol"` - } `json:"https_basic,omitempty"` - CertName string `json:"cert_name"` - Ssl string `json:"ssl"` - SslStapling string `json:"ssl_stapling"` - } `json:"returnObj,omitempty"` + ReturnObj *DomainDetail `json:"returnObj,omitempty"` } func (c *Client) QueryDomainDetail(req *QueryDomainDetailRequest) (*QueryDomainDetailResponse, error) { diff --git a/pkg/sdk3rd/ctyun/cdn/api_query_domain_list.go b/pkg/sdk3rd/ctyun/cdn/api_query_domain_list.go new file mode 100644 index 000000000..d2a316e93 --- /dev/null +++ b/pkg/sdk3rd/ctyun/cdn/api_query_domain_list.go @@ -0,0 +1,67 @@ +package cdn + +import ( + "context" + "net/http" + "strconv" +) + +type QueryDomainListRequest struct { + Page *int32 `json:"page,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + Domain *string `json:"domain,omitempty"` + ProductCode *string `json:"product_code,omitempty"` + Status *int32 `json:"status,omitempty"` + AreaScope *int32 `json:"area_scope,omitempty"` +} + +type QueryDomainListResponse struct { + apiResponseBase + + ReturnObj *struct { + Results []*DomainRecord `json:"result,omitempty"` + Page int32 `json:"page,omitempty"` + PageSize int32 `json:"page_size,omitempty"` + PageCount int32 `json:"page_count,omitempty"` + Total int32 `json:"total,omitempty"` + } `json:"returnObj,omitempty"` +} + +func (c *Client) QueryDomainList(req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + return c.QueryDomainListWithContext(context.Background(), req) +} + +func (c *Client) QueryDomainListWithContext(ctx context.Context, req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v1/domain/query-domain-list") + if err != nil { + return nil, err + } else { + if req.Page != nil { + httpreq.SetQueryParam("page", strconv.Itoa(int(*req.Page))) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_size", strconv.Itoa(int(*req.PageSize))) + } + if req.Domain != nil { + httpreq.SetQueryParam("domain", *req.Domain) + } + if req.ProductCode != nil { + httpreq.SetQueryParam("product_code", *req.ProductCode) + } + if req.Status != nil { + httpreq.SetQueryParam("status", strconv.Itoa(int(*req.Status))) + } + if req.AreaScope != nil { + httpreq.SetQueryParam("area_scope", strconv.Itoa(int(*req.AreaScope))) + } + + httpreq.SetContext(ctx) + } + + result := &QueryDomainListResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/ctyun/cdn/types.go b/pkg/sdk3rd/ctyun/cdn/types.go index 32054a0c6..f495caeda 100644 --- a/pkg/sdk3rd/ctyun/cdn/types.go +++ b/pkg/sdk3rd/ctyun/cdn/types.go @@ -70,6 +70,32 @@ func (r *apiResponseBase) GetErrorMessage() string { var _ apiResponse = (*apiResponseBase)(nil) +type DomainRecord struct { + Domain string `json:"domain"` + Cname string `json:"cname"` + ProductCode string `json:"product_code"` + ProductName string `json:"product_name"` + AreaScope int32 `json:"area_scope"` + Status int32 `json:"status"` + CreatedTime int64 `json:"insert_date"` +} + +type DomainDetail struct { + DomainRecord + HttpsStatus string `json:"https_status"` + HttpsBasic *DomainHttpsBasicConfig `json:"https_basic,omitempty"` + CertName string `json:"cert_name"` + Ssl string `json:"ssl"` + SslStapling string `json:"ssl_stapling"` +} + +type DomainHttpsBasicConfig struct { + HttpsForce string `json:"https_force"` + HttpForce string `json:"http_force"` + ForceStatus string `json:"force_status"` + OriginProtocol string `json:"origin_protocol"` +} + type CertRecord struct { Id int64 `json:"id"` Name string `json:"name"` diff --git a/pkg/utils/cert/hostname/hostname.go b/pkg/utils/cert/hostname/hostname.go index 35e593e5e..69ddf4bbd 100644 --- a/pkg/utils/cert/hostname/hostname.go +++ b/pkg/utils/cert/hostname/hostname.go @@ -19,8 +19,8 @@ func IsMatch(match, candidate string) bool { return false } - if !strings.Contains(match, "*") { - return strings.EqualFold(match, candidate) + if strings.EqualFold(match, candidate) { + return true } mockCert := &x509.Certificate{} diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudCDN.tsx index db9257852..5de7bd374 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudCDN.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 BizDeployNodeConfigFieldsProviderCTCCCloudCDN = () => { const { i18n, t } = useTranslation(); @@ -15,24 +20,52 @@ const BizDeployNodeConfigFieldsProviderCTCCCloudCDN = () => { [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 ( <> + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +73,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderCTCCCloudCDN, { From 73fa214c9fe6ffcd35201553a37d76d1b6730340 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:02 +0800 Subject: [PATCH 11/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to ctyun icdn --- .../certdeploy/deployers/sp_ctcccloud_icdn.go | 7 +- .../providers/aliyun-alb/aliyun_alb.go | 2 +- .../providers/aliyun-apigw/aliyun_apigw.go | 2 +- .../providers/aliyun-cdn/aliyun_cdn.go | 2 +- .../providers/aliyun-dcdn/aliyun_dcdn.go | 2 +- .../baiducloud-cdn/baiducloud_cdn.go | 2 +- .../baotapanelgo-site/baotapanelgo_site.go | 2 +- .../providers/baotawaf-site/baotawaf_site.go | 2 +- .../providers/byteplus-cdn/byteplus_cdn.go | 2 +- .../providers/ctcccloud-ao/ctcccloud_ao.go | 2 +- .../providers/ctcccloud-cdn/ctcccloud_cdn.go | 4 +- .../providers/ctcccloud-icdn/consts.go | 10 + .../ctcccloud-icdn/ctcccloud_icdn.go | 182 +++++++++++++++--- .../ctcccloud-icdn/ctcccloud_icdn_test.go | 7 +- .../huaweicloud-cdn/huaweicloud_cdn.go | 2 +- .../huaweicloud-waf/huaweicloud_waf.go | 4 +- .../providers/jdcloud-cdn/jdcloud_cdn.go | 2 +- .../providers/ksyun-cdn/ksyun_cdn.go | 2 +- .../tencentcloud-cdn/tencentcloud_cdn.go | 2 +- .../tencentcloud-ecdn/tencentcloud_ecdn.go | 2 +- .../volcengine-cdn/volcengine_cdn.go | 2 +- .../volcengine-live/volcengine_live.go | 2 +- .../providers/1panel-ssl/1panel_ssl.go | 4 +- .../baiducloud-cert/baiducloud_cert.go | 2 +- .../providers/ctcccloud-cms/ctcccloud_cms.go | 4 +- .../huaweicloud-elb/huaweicloud_elb.go | 2 +- .../huaweicloud-scm/huaweicloud_scm.go | 2 +- .../huaweicloud-waf/huaweicloud_waf.go | 2 +- .../providers/jdcloud-ssl/jdcloud_ssl.go | 2 +- .../providers/qiniu-sslcert/qiniu_sslcert.go | 2 +- .../rainyun-sslcenter/rainyun_sslcenter.go | 6 +- .../providers/ucloud-ussl/ucloud_ussl.go | 2 +- .../ctyun/icdn/api_query_domain_detail.go | 18 +- .../ctyun/icdn/api_query_domain_list.go | 67 +++++++ pkg/sdk3rd/ctyun/icdn/types.go | 26 +++ ...yNodeConfigFieldsProviderCTCCCloudICDN.tsx | 69 ++++++- 36 files changed, 365 insertions(+), 89 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/ctcccloud-icdn/consts.go create mode 100644 pkg/sdk3rd/ctyun/icdn/api_query_domain_list.go diff --git a/internal/certdeploy/deployers/sp_ctcccloud_icdn.go b/internal/certdeploy/deployers/sp_ctcccloud_icdn.go index bce31b39d..44b160488 100644 --- a/internal/certdeploy/deployers/sp_ctcccloud_icdn.go +++ b/internal/certdeploy/deployers/sp_ctcccloud_icdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := ctcccloudicdn.NewSSLDeployerProvider(&ctcccloudicdn.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-alb/aliyun_alb.go b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go index 65836437e..96619421f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go @@ -320,7 +320,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo } } - // 遍历查询监听证书,并找出需要解除关联的证书 + // 查询监听证书,并找出需要解除关联的证书 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlistenercertificates // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-getusercertificatedetail certificateIsAlreadyAssociated := false diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go index 486596bf3..0d3a3c158 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -146,7 +146,7 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s return errors.New("config `domain` is required") } - // 遍历查询域名列表,获取域名 ID + // 查询域名列表,获取域名 ID // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains var domainId string listDomainsPageNumber := int32(1) 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 b8bbf1a51..d5bc06697 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -185,7 +185,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表 + // 查询域名列表 // REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-describeuserdomains describeUserDomainsPageNumber := int32(1) describeUserDomainsPageSize := int32(500) diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index a1c541736..f703619d5 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -185,7 +185,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表 + // 查询域名列表 // REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-describedcdnuserdomains describeUserDomainsPageNumber := int32(1) describeUserDomainsPageSize := int32(500) 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 f360e2e77..75100ecac 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go @@ -151,7 +151,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表 + // 查询域名列表 // REF: https://cloud.baidu.com/doc/CDN/s/sjwvyewt1 listDomainsMarker := "" for { diff --git a/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go b/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go index 59a543d76..9b925b0a6 100644 --- a/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go +++ b/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go @@ -74,7 +74,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, fmt.Errorf("failed to execute sdk request 'bt.PanelGetConfig': %w", err) } - // 遍历查询网站列表,获取网站 ID + // 查询网站列表,获取网站 ID var siteId int32 datalistGetDataListPage := int32(1) datalistGetDataListLimit := int32(10) diff --git a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go index d1408d9e1..1ea80534e 100644 --- a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go +++ b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go @@ -68,7 +68,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.config.SitePort = 443 } - // 遍历获取网站列表,获取网站 ID + // 查询网站列表,获取网站 ID // REF: https://support.huaweicloud.com/api-waf/ListHost.html siteId := "" getSitListPage := int32(1) 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 8a7180a21..1ace82c0e 100644 --- a/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go +++ b/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -153,7 +153,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke 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) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go index da7c71a28..591a63576 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go @@ -169,7 +169,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表 + // 查询域名列表 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13816&data=174&isNormal=1&vid=167 queryDomainsPage := int32(1) queryDomainsPageSize := int32(100) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go index f8a20f630..b21777b15 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go @@ -168,8 +168,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13816&data=174&isNormal=1&vid=167 + // 查询域名列表 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11307&data=161&isNormal=1&vid=154 queryDomainListPage := int32(1) queryDomainListPageSize := int32(100) for { diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/consts.go b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/consts.go new file mode 100644 index 000000000..0ccc5ad71 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/consts.go @@ -0,0 +1,10 @@ +package ctcccloudicdn + +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/ctcccloud-icdn/ctcccloud_icdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go index ad589ce08..7a3a2c525 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go @@ -5,12 +5,15 @@ import ( "errors" "fmt" "log/slog" + "strings" "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/ctcccloud-icdn" ctyunicdn "github.com/certimate-go/certimate/pkg/sdk3rd/ctyun/icdn" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -18,6 +21,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"` } @@ -66,10 +72,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -78,33 +80,165 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 查询域名配置信息 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10849&data=173&isNormal=1&vid=166 - queryDomainDetailReq := &ctyunicdn.QueryDomainDetailRequest{ - Domain: lo.ToPtr(d.config.Domain), - } - queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) - d.logger.Debug("sdk request 'icdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryDomainDetail': %w", err) + // 获取待部署的域名列表 + 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("could not find any 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("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 修改域名配置 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10853&data=173&isNormal=1&vid=166 - updateDomainReq := &ctyunicdn.UpdateDomainRequest{ - Domain: lo.ToPtr(d.config.Domain), - HttpsStatus: lo.ToPtr("on"), - CertName: lo.ToPtr(upres.CertName), - } - updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) - d.logger.Debug("sdk request 'icdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'icdn.UpdateDomain': %w", err) + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no icdn domains to deploy") + } else { + d.logger.Info("found icdn 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, upres.CertId, upres.CertName); 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://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10852&data=173&isNormal=1&vid=166 + queryDomainsPage := int32(1) + queryDomainsPageSize := int32(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + queryDomainListReq := &ctyunicdn.QueryDomainListRequest{ + Page: lo.ToPtr(queryDomainsPage), + PageSize: lo.ToPtr(queryDomainsPageSize), + ProductCode: lo.ToPtr("006"), + } + queryDomainListResp, err := d.sdkClient.QueryDomainList(queryDomainListReq) + d.logger.Debug("sdk request 'cdn.QueryDomainList'", slog.Any("request", queryDomainListReq), slog.Any("response", queryDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainList': %w", err) + } + + if queryDomainListResp.ReturnObj != nil { + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainInfo := range queryDomainListResp.ReturnObj.Results { + if lo.Contains(ignoredStatuses, domainInfo.Status) { + continue + } + + domains = append(domains, domainInfo.Domain) + } + } + + if queryDomainListResp.ReturnObj == nil || len(queryDomainListResp.ReturnObj.Results) < int(queryDomainsPageSize) { + break + } else { + queryDomainsPage++ + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId, cloudCertName string) error { + // 查询域名配置信息 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10849&data=173&isNormal=1&vid=166 + queryDomainDetailReq := &ctyunicdn.QueryDomainDetailRequest{ + Domain: lo.ToPtr(domain), + } + queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) + d.logger.Debug("sdk request 'icdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'icdn.QueryDomainDetail': %w", err) + } + + // 修改域名配置 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10853&data=173&isNormal=1&vid=166 + updateDomainReq := &ctyunicdn.UpdateDomainRequest{ + Domain: lo.ToPtr(domain), + HttpsStatus: lo.ToPtr("on"), + CertName: lo.ToPtr(cloudCertName), + } + updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) + d.logger.Debug("sdk request 'icdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'icdn.UpdateDomain': %w", err) + } + + return nil +} + func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunicdn.Client, error) { return ctyunicdn.NewClient(accessKeyId, secretAccessKey) } diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn_test.go b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn_test.go index 8c7665dfa..b7544a85d 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn_test.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn_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/huaweicloud-cdn/huaweicloud_cdn.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index 63e990fbb..4e481c544 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -183,7 +183,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表 + // 查询域名列表 // REF: https://support.huaweicloud.com/api-cdn/ListDomains.html listDomainsPageNumber := int32(1) listDomainsPageSize := int32(100) diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go b/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go index d7e932fbb..dacce0bea 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go @@ -171,7 +171,7 @@ func (d *SSLDeployerProvider) deployToCloudServer(ctx context.Context, certPEM s d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 遍历查询云模式防护域名列表,获取防护域名 ID + // 查询云模式防护域名列表,获取防护域名 ID // REF: https://support.huaweicloud.com/api-waf/ListHost.html hostId := "" listHostPage := int32(1) @@ -246,7 +246,7 @@ func (d *SSLDeployerProvider) deployToPremiumHost(ctx context.Context, certPEM s d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 遍历查询独享模式域名列表,获取防护域名 ID + // 查询独享模式域名列表,获取防护域名 ID // REF: https://support.huaweicloud.com/api-waf/ListPremiumHost.html hostId := "" listPremiumHostPage := int32(1) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go index bb7a84014..976816ed3 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go @@ -172,7 +172,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表 + // 查询域名列表 // REF: https://docs.jdcloud.com/cn/cdn/api/getdomainlist getDomainListPageNumber := 1 getDomainListPageSize := 50 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 956bfa0d1..1f43d2018 100644 --- a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go @@ -83,7 +83,7 @@ func (d *SSLDeployerProvider) deployToDomain(ctx context.Context, certPEM string return errors.New("config `domain` is required") } - // 遍历查询域名列表,获取域名 ID + // 查询域名列表,获取域名 ID // https://docs.ksyun.com/documents/198 var domainId string getCdnDomainsPageNumber := int32(1) 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 bc92ca0dd..26974375f 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -162,7 +162,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke 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) 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 2155132b2..0337c2669 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -162,7 +162,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke 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) 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 8e30e2857..9787e597f 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -156,7 +156,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke 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) 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 c1054793f..6427eb435 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -170,7 +170,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { domains := make([]string, 0) - // 遍历查询域名列表 + // 查询域名列表 // REF: https://www.volcengine.com/docs/6469/1126815 listDomainDetailPageNum := int32(1) listDomainDetailPageSize := int32(1000) diff --git a/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go b/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go index e656323ff..416ed65fb 100644 --- a/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go +++ b/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go @@ -59,7 +59,7 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历证书列表,避免重复上传 + // 避免重复上传 if res, err := m.findCertIfExists(ctx, certPEM, privkeyPEM); err != nil { return nil, err } else if res != nil { @@ -106,7 +106,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey panic("sdk client is not implemented") } - // 遍历证书列表,获取刚刚上传证书 ID + // 获取刚刚上传证书 ID if res, err := m.findCertIfExists(ctx, certPEM, privkeyPEM); err != nil { return nil, err } else if res == nil { diff --git a/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go b/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go index a99b1a8d5..e27e5d852 100644 --- a/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go +++ b/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go @@ -60,7 +60,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, err } - // 遍历证书列表,避免重复上传 + // 查看证书列表 // REF: https://cloud.baidu.com/doc/Reference/s/Gjwvz27xu#35-%E6%9F%A5%E7%9C%8B%E8%AF%81%E4%B9%A6%E5%88%97%E8%A1%A8%E8%AF%A6%E6%83%85 listCertDetail, err := m.sdkClient.ListCertDetail() m.logger.Debug("sdk request 'cert.ListCertDetail'", slog.Any("response", listCertDetail)) diff --git a/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go b/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go index 104ccce62..e179566c8 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go @@ -58,7 +58,7 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历证书列表,避免重复上传 + // 避免重复上传 if res, _ := m.findCertIfExists(ctx, certPEM); res != nil { return res, nil } @@ -98,7 +98,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'cms.UploadCertificate': %w", err) } - // 遍历证书列表,获取刚刚上传证书 ID + // 获取刚刚上传证书 ID if res, err := m.findCertIfExists(ctx, certPEM); err != nil { return nil, err } else if res == nil { diff --git a/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go b/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go index 10c2a9f4a..de1d13fc4 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go @@ -67,7 +67,7 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历查询已有证书,避免重复上传 + // 查询已有证书,避免重复上传 // REF: https://support.huaweicloud.com/api-elb/ListCertificates.html listCertificatesLimit := int32(2000) var listCertificatesMarker *string = nil diff --git a/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go b/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go index 674ffbe11..9d815d336 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go @@ -70,7 +70,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, err } - // 遍历查询已有证书,避免重复上传 + // 查询已有证书,避免重复上传 // REF: https://support.huaweicloud.com/api-ccm/ListCertificates.html // REF: https://support.huaweicloud.com/api-ccm/ExportCertificate_0.html listCertificatesLimit := int32(50) diff --git a/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go b/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go index 7db54c57c..06522586b 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go @@ -67,7 +67,7 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历查询已有证书,避免重复上传 + // 查询已有证书,避免重复上传 // REF: https://support.huaweicloud.com/api-waf/ListCertificates.html // REF: https://support.huaweicloud.com/api-waf/ShowCertificate.html listCertificatesPage := int32(1) diff --git a/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go b/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go index 7f3861f27..1a496bf98 100644 --- a/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go +++ b/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go @@ -71,7 +71,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey privkeyPEM = strings.ReplaceAll(privkeyPEM, "\n", "\r\n") privkeyPEM = privkeyPEM + "\r\n" - // 遍历查看证书列表,避免重复上传 + // 查看证书列表 // REF: https://docs.jdcloud.com/cn/ssl-certificate/api/describecerts describeCertsPageNumber := 1 describeCertsPageSize := 10 diff --git a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go index dcbe26b03..dd0297ded 100644 --- a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go +++ b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go @@ -67,7 +67,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 生成新证书名(需符合七牛云命名规则) certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) - // 遍历查询已有证书,避免重复上传 + // 查询已有证书,避免重复上传 getSslCertListMarker := "" getSslCertListLimit := int32(200) for { diff --git a/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go b/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go index 9b56fcb24..8170befe2 100644 --- a/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go +++ b/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go @@ -51,7 +51,7 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { } func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - // 遍历证书列表,避免重复上传 + // 避免重复上传 if res, err := m.findCertIfExists(ctx, certPEM); err != nil { return nil, err } else if res != nil { @@ -71,7 +71,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.Create': %w", err) } - // 遍历证书列表,获取刚刚上传证书 ID + // 获取刚刚上传证书 ID if res, err := m.findCertIfExists(ctx, certPEM); err != nil { return nil, err } else if res == nil { @@ -88,7 +88,7 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, err } - // 遍历 SSL 证书列表 + // 获取 SSL 证书列表 // REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943046 // REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943048 sslCenterListPage := int32(1) diff --git a/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go b/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go index 83fe9deac..79a485326 100644 --- a/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go +++ b/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go @@ -115,7 +115,7 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, err } - // 遍历获取用户证书列表 + // 查询用户证书列表 // REF: https://docs.ucloud.cn/api/usslcertificate-api/get_certificate_list // REF: https://docs.ucloud.cn/api/usslcertificate-api/download_certificate getCertificateListPage := int(1) diff --git a/pkg/sdk3rd/ctyun/icdn/api_query_domain_detail.go b/pkg/sdk3rd/ctyun/icdn/api_query_domain_detail.go index ace1bddb6..bf735c2ae 100644 --- a/pkg/sdk3rd/ctyun/icdn/api_query_domain_detail.go +++ b/pkg/sdk3rd/ctyun/icdn/api_query_domain_detail.go @@ -14,23 +14,7 @@ type QueryDomainDetailRequest struct { type QueryDomainDetailResponse struct { apiResponseBase - ReturnObj *struct { - Domain string `json:"domain"` - ProductCode string `json:"product_code"` - Status int32 `json:"status"` - AreaScope int32 `json:"area_scope"` - Cname string `json:"cname"` - HttpsStatus string `json:"https_status"` - HttpsBasic *struct { - HttpsForce string `json:"https_force"` - HttpForce string `json:"http_force"` - ForceStatus string `json:"force_status"` - OriginProtocol string `json:"origin_protocol"` - } `json:"https_basic,omitempty"` - CertName string `json:"cert_name"` - Ssl string `json:"ssl"` - SslStapling string `json:"ssl_stapling"` - } `json:"returnObj,omitempty"` + ReturnObj *DomainDetail `json:"returnObj,omitempty"` } func (c *Client) QueryDomainDetail(req *QueryDomainDetailRequest) (*QueryDomainDetailResponse, error) { diff --git a/pkg/sdk3rd/ctyun/icdn/api_query_domain_list.go b/pkg/sdk3rd/ctyun/icdn/api_query_domain_list.go new file mode 100644 index 000000000..318fea11b --- /dev/null +++ b/pkg/sdk3rd/ctyun/icdn/api_query_domain_list.go @@ -0,0 +1,67 @@ +package icdn + +import ( + "context" + "net/http" + "strconv" +) + +type QueryDomainListRequest struct { + Page *int32 `json:"page,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + Domain *string `json:"domain,omitempty"` + ProductCode *string `json:"product_code,omitempty"` + Status *int32 `json:"status,omitempty"` + AreaScope *int32 `json:"area_scope,omitempty"` +} + +type QueryDomainListResponse struct { + apiResponseBase + + ReturnObj *struct { + Results []*DomainRecord `json:"result,omitempty"` + Page int32 `json:"page,omitempty"` + PageSize int32 `json:"page_size,omitempty"` + PageCount int32 `json:"page_count,omitempty"` + Total int32 `json:"total,omitempty"` + } `json:"returnObj,omitempty"` +} + +func (c *Client) QueryDomainList(req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + return c.QueryDomainListWithContext(context.Background(), req) +} + +func (c *Client) QueryDomainListWithContext(ctx context.Context, req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v1/domain/query-domain-list") + if err != nil { + return nil, err + } else { + if req.Page != nil { + httpreq.SetQueryParam("page", strconv.Itoa(int(*req.Page))) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_size", strconv.Itoa(int(*req.PageSize))) + } + if req.Domain != nil { + httpreq.SetQueryParam("domain", *req.Domain) + } + if req.ProductCode != nil { + httpreq.SetQueryParam("product_code", *req.ProductCode) + } + if req.Status != nil { + httpreq.SetQueryParam("status", strconv.Itoa(int(*req.Status))) + } + if req.AreaScope != nil { + httpreq.SetQueryParam("area_scope", strconv.Itoa(int(*req.AreaScope))) + } + + httpreq.SetContext(ctx) + } + + result := &QueryDomainListResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/ctyun/icdn/types.go b/pkg/sdk3rd/ctyun/icdn/types.go index 0d1e8fb64..a48ed4658 100644 --- a/pkg/sdk3rd/ctyun/icdn/types.go +++ b/pkg/sdk3rd/ctyun/icdn/types.go @@ -70,6 +70,32 @@ func (r *apiResponseBase) GetErrorMessage() string { var _ apiResponse = (*apiResponseBase)(nil) +type DomainRecord struct { + Domain string `json:"domain"` + Cname string `json:"cname"` + ProductCode string `json:"product_code"` + ProductName string `json:"product_name"` + AreaScope int32 `json:"area_scope"` + Status int32 `json:"status"` + CreatedTime int64 `json:"insert_date"` +} + +type DomainDetail struct { + DomainRecord + HttpsStatus string `json:"https_status"` + HttpsBasic *DomainHttpsBasicConfig `json:"https_basic,omitempty"` + CertName string `json:"cert_name"` + Ssl string `json:"ssl"` + SslStapling string `json:"ssl_stapling"` +} + +type DomainHttpsBasicConfig struct { + HttpsForce string `json:"https_force"` + HttpForce string `json:"http_force"` + ForceStatus string `json:"force_status"` + OriginProtocol string `json:"origin_protocol"` +} + type CertRecord struct { Id int64 `json:"id"` Name string `json:"name"` diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudICDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudICDN.tsx index 86bb72107..cc55ff314 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudICDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudICDN.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 BizDeployNodeConfigFieldsProviderCTCCCloudICDN = () => { const { i18n, t } = useTranslation(); @@ -15,24 +20,52 @@ const BizDeployNodeConfigFieldsProviderCTCCCloudICDN = () => { [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 ( <> + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +73,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderCTCCCloudICDN, { From 1c252310e5eca3b0c1090c5c80f4c6535708b8f2 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:03 +0800 Subject: [PATCH 12/30] refactor: clean code --- .../certdeploy/deployers/sp_baishan_cdn.go | 7 +- .../certdeploy/deployers/sp_wangsu_cdn.go | 7 +- .../certdeploy/deployers/sp_wangsu_cdnpro.go | 15 +- .../providers/aliyun-esa/internal/lego.go | 38 ++-- .../providers/baiducloud/internal/lego.go | 18 +- .../providers/dnsla/internal/lego.go | 36 ++-- .../providers/dynv6/internal/lego.go | 2 +- .../providers/jdcloud/internal/lego.go | 26 +-- .../providers/aliyun-alb/aliyun_alb.go | 58 ++++--- .../providers/aliyun-apigw/aliyun_apigw.go | 95 +++++----- .../aliyun-cas-deploy/aliyun_cas_deploy.go | 7 +- .../providers/aliyun-cdn/aliyun_cdn.go | 38 ++-- .../providers/aliyun-clb/aliyun_clb.go | 21 +-- .../providers/aliyun-dcdn/aliyun_dcdn.go | 24 +-- .../providers/aliyun-ga/aliyun_ga.go | 54 +++--- .../providers/aliyun-nlb/aliyun_nlb.go | 21 +-- .../baiducloud-appblb/baiducloud_appblb.go | 2 +- .../baiducloud-blb/baiducloud_blb.go | 2 +- .../baiducloud-cdn/baiducloud_cdn.go | 4 +- .../providers/baishan-cdn/baishan_cdn.go | 5 +- .../providers/baishan-cdn/baishan_cdn_test.go | 5 +- .../providers/baishan-cdn/consts.go | 6 + .../baotapanelgo-site/baotapanelgo_site.go | 85 ++++----- .../providers/baotawaf-site/baotawaf_site.go | 86 ++++----- .../providers/bunny-cdn/bunny_cdn.go | 2 +- .../providers/byteplus-cdn/byteplus_cdn.go | 20 +-- .../providers/ctcccloud-ao/ctcccloud_ao.go | 38 ++-- .../providers/ctcccloud-cdn/ctcccloud_cdn.go | 46 ++--- .../providers/ctcccloud-elb/ctcccloud_elb.go | 10 +- .../ctcccloud-icdn/ctcccloud_icdn.go | 38 ++-- .../huaweicloud-cdn/huaweicloud_cdn.go | 38 ++-- .../huaweicloud-elb/huaweicloud_elb.go | 23 +-- .../huaweicloud-waf/huaweicloud_waf.go | 56 +++--- .../providers/jdcloud-alb/jdcloud_alb.go | 8 +- .../providers/jdcloud-cdn/jdcloud_cdn.go | 10 +- .../providers/jdcloud-vod/jdcloud_vod.go | 78 +++++---- .../providers/ksyun-cdn/ksyun_cdn.go | 130 +++++++------- .../tencentcloud-cdn/tencentcloud_cdn.go | 28 +-- .../tencentcloud-clb/tencentcloud_clb.go | 12 +- .../tencentcloud-cos/tencentcloud_cos.go | 49 +++--- .../tencentcloud-ecdn/tencentcloud_ecdn.go | 28 +-- .../tencentcloud-eo/tencentcloud_eo.go | 7 +- .../tencentcloud-gaap/tencentcloud_gaap.go | 2 +- .../tencentcloud_ssl_deploy.go | 21 +-- .../tencentcloud_ssl_update.go | 37 ++-- .../providers/ucloud-ucdn/ucloud_ucdn.go | 2 +- .../volcengine-alb/volcengine_alb.go | 14 +- .../volcengine-cdn/volcengine_cdn.go | 22 ++- .../volcengine-clb/volcengine_clb.go | 14 +- .../volcengine-dcdn/volcengine_dcdn.go | 2 +- .../volcengine-live/volcengine_live.go | 26 +-- .../providers/wangsu-cdn/consts.go | 6 + .../providers/wangsu-cdn/wangsu_cdn.go | 3 + .../providers/wangsu-cdnpro/consts.go | 6 + .../providers/wangsu-cdnpro/wangsu_cdnpro.go | 3 + .../providers/1panel-ssl/1panel_ssl.go | 70 ++++---- .../providers/aliyun-cas/aliyun_cas.go | 100 +++++------ .../providers/aliyun-slb/aliyun_slb.go | 12 +- .../ssl-manager/providers/aws-acm/aws_acm.go | 31 ++-- .../ssl-manager/providers/aws-iam/aws_iam.go | 31 ++-- .../azure-keyvault/azure_keyvault.go | 28 +-- .../baiducloud-cert/baiducloud_cert.go | 26 +-- .../providers/byteplus-cdn/byteplus_cdn.go | 55 +++--- .../providers/ctcccloud-ao/ctcccloud_ao.go | 103 +++++------ .../providers/ctcccloud-cdn/ctcccloud_cdn.go | 103 +++++------ .../providers/ctcccloud-cms/ctcccloud_cms.go | 84 ++++----- .../providers/ctcccloud-elb/ctcccloud_elb.go | 8 +- .../ctcccloud-icdn/ctcccloud_icdn.go | 103 +++++------ .../ctcccloud-lvdn/ctcccloud_lvdn.go | 103 +++++------ .../huaweicloud-elb/huaweicloud_elb.go | 35 ++-- .../huaweicloud-scm/huaweicloud_scm.go | 94 +++++----- .../huaweicloud-waf/huaweicloud_waf.go | 54 +++--- .../providers/jdcloud-ssl/jdcloud_ssl.go | 24 +-- .../providers/qiniu-sslcert/qiniu_sslcert.go | 91 +++++----- .../rainyun-sslcenter/rainyun_sslcenter.go | 83 ++++----- .../providers/ucloud-ussl/ucloud_ussl.go | 164 +++++++++--------- .../volcengine-cdn/volcengine_cdn.go | 59 +++---- .../volcengine-live/volcengine_live.go | 8 +- .../wangsu-certificate/wangsu_certificate.go | 16 +- pkg/sdk3rd/baishan/api_get_domain_list.go | 55 ++++++ pkg/sdk3rd/baishan/types.go | 11 ++ ...ployNodeConfigFieldsProviderBaishanCDN.tsx | 69 ++++++-- ...eployNodeConfigFieldsProviderWangsuCDN.tsx | 65 ++++++- ...oyNodeConfigFieldsProviderWangsuCDNPro.tsx | 61 ++++++- 84 files changed, 1727 insertions(+), 1460 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/baishan-cdn/consts.go create mode 100644 pkg/core/ssl-deployer/providers/wangsu-cdn/consts.go create mode 100644 pkg/core/ssl-deployer/providers/wangsu-cdnpro/consts.go create mode 100644 pkg/sdk3rd/baishan/api_get_domain_list.go diff --git a/internal/certdeploy/deployers/sp_baishan_cdn.go b/internal/certdeploy/deployers/sp_baishan_cdn.go index 693a8458f..d458db203 100644 --- a/internal/certdeploy/deployers/sp_baishan_cdn.go +++ b/internal/certdeploy/deployers/sp_baishan_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := baishancdn.NewSSLDeployerProvider(&baishancdn.SSLDeployerProviderConfig{ - ApiToken: credentials.ApiToken, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), + ApiToken: credentials.ApiToken, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_wangsu_cdn.go b/internal/certdeploy/deployers/sp_wangsu_cdn.go index 9e4ad7daa..964f782dd 100644 --- a/internal/certdeploy/deployers/sp_wangsu_cdn.go +++ b/internal/certdeploy/deployers/sp_wangsu_cdn.go @@ -20,9 +20,10 @@ func init() { } provider, err := wangsucdn.NewSSLDeployerProvider(&wangsucdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Domains: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "domains"), ";"), func(s string, _ int) bool { return s != "" }), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domains: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "domains"), ";"), func(s string, _ int) bool { return s != "" }), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_wangsu_cdnpro.go b/internal/certdeploy/deployers/sp_wangsu_cdnpro.go index 1c70b545a..081441004 100644 --- a/internal/certdeploy/deployers/sp_wangsu_cdnpro.go +++ b/internal/certdeploy/deployers/sp_wangsu_cdnpro.go @@ -17,13 +17,14 @@ func init() { } provider, err := wangsucdnpro.NewSSLDeployerProvider(&wangsucdnpro.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ApiKey: credentials.ApiKey, - Environment: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "environment", "production"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), - CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), - WebhookId: xmaps.GetString(options.ProviderExtendedConfig, "webhookId"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ApiKey: credentials.ApiKey, + Environment: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "environment", "production"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), + WebhookId: xmaps.GetString(options.ProviderExtendedConfig, "webhookId"), }) return provider, err }) diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go index 0694634c1..1319d6765 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa/internal/lego.go @@ -106,7 +106,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } siteName := dns01.UnFqdn(authZone) - siteID, err := d.getSiteId(siteName) + siteID, err := d.findSiteIdByName(siteName) if err != nil { return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", siteName, err) } @@ -158,17 +158,17 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getSiteId(siteName string) (int64, error) { - pageNumber := 1 - pageSize := 500 +func (d *DNSProvider) findSiteIdByName(siteName string) (int64, error) { + aliListSitesPageNumber := 1 + aliListSitesPageSize := 500 for { // REF: https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-listsites aliListSitesReq := &aliesa.ListSitesRequest{ SiteName: tea.String(siteName), SiteSearchType: tea.String("exact"), - PageNumber: tea.Int32(int32(pageNumber)), - PageSize: tea.Int32(int32(pageSize)), AccessType: tea.String("NS"), + PageNumber: tea.Int32(int32(aliListSitesPageNumber)), + PageSize: tea.Int32(int32(aliListSitesPageSize)), } aliListSitesResp, err := d.client.ListSites(aliListSitesReq) if err != nil { @@ -177,20 +177,20 @@ func (d *DNSProvider) getSiteId(siteName string) (int64, error) { if aliListSitesResp.Body == nil { break - } else { - for _, site := range aliListSitesResp.Body.Sites { - if *site.GetSiteName() == siteName { - return *site.GetSiteId(), nil - } - } - - if len(aliListSitesResp.Body.Sites) < pageSize { - break - } - - pageNumber++ } + + for _, siteItem := range aliListSitesResp.Body.Sites { + if *siteItem.GetSiteName() == siteName { + return *siteItem.GetSiteId(), nil + } + } + + if len(aliListSitesResp.Body.Sites) < aliListSitesPageSize { + break + } + + aliListSitesPageNumber++ } - return 0, errors.New("site not found") + return 0, fmt.Errorf("could not find site '%s'", siteName) } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go index 794bbf749..eee099510 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/baiducloud/internal/lego.go @@ -145,31 +145,31 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { } func (d *DNSProvider) findDNSRecord(zoneName, subDomain, tokenValue string) (*bcedns.Record, error) { - pageMarker := "" - pageSize := 1000 + bceListRecordPageMarker := "" for { // REF: https://cloud.baidu.com/doc/DNS/s/El4s7lssr#%E6%9F%A5%E8%AF%A2%E8%A7%A3%E6%9E%90%E8%AE%B0%E5%BD%95%E5%88%97%E8%A1%A8 bceListRecordReq := &bcedns.ListRecordRequest{} bceListRecordReq.Rr = subDomain - bceListRecordReq.Marker = pageMarker - bceListRecordReq.MaxKeys = pageSize + bceListRecordReq.Marker = bceListRecordPageMarker + bceListRecordReq.MaxKeys = 1000 - ceListRecordResp, err := d.client.ListRecord(zoneName, bceListRecordReq) + bceListRecordResp, err := d.client.ListRecord(zoneName, bceListRecordReq) if err != nil { return nil, err } - for _, record := range ceListRecordResp.Records { + for _, record := range bceListRecordResp.Records { if record.Type == "TXT" && record.Rr == subDomain && record.Value == tokenValue { return &record, nil } } - pageMarker = ceListRecordResp.NextMarker - if pageMarker == "" { + if bceListRecordResp.NextMarker == "" { break } + + bceListRecordPageMarker = bceListRecordResp.NextMarker } - return nil, errors.New("record not found") + return nil, errors.New("could not find record") } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go index f0707d9e2..52f3441ec 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/dnsla/internal/lego.go @@ -102,7 +102,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("dnsla: %w", err) } - zone, err := d.getDNSZone(dns01.UnFqdn(authZone)) + zone, err := d.findZone(dns01.UnFqdn(authZone)) if err != nil { return fmt.Errorf("dnsla: error when list zones: %w", err) } @@ -149,34 +149,36 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getDNSZone(zoneName string) (*dnslasdk.DomainRecord, error) { - pageIndex := int32(1) - pageSize := int32(100) +func (d *DNSProvider) findZone(zoneName string) (*dnslasdk.DomainRecord, error) { + dnslaListDomainsPageIndex := 1 + dnslaListDomainsPageSize := 100 for { // REF: https://www.dnsla.cn/docs/ApiDoc dnslaListDomainsReq := &dnslasdk.ListDomainsRequest{ - PageIndex: &pageIndex, - PageSize: &pageSize, + PageIndex: lo.ToPtr(int32(dnslaListDomainsPageIndex)), + PageSize: lo.ToPtr(int32(dnslaListDomainsPageSize)), } dnslaListDomainsResp, err := d.client.ListDomains(dnslaListDomainsReq) if err != nil { return nil, err } - if dnslaListDomainsResp.Data != nil { - for _, item := range dnslaListDomainsResp.Data.Results { - if strings.TrimRight(item.Domain, ".") == zoneName || strings.TrimRight(item.DisplayDomain, ".") == zoneName { - return item, nil - } - } - } - - if dnslaListDomainsResp.Data == nil || len(dnslaListDomainsResp.Data.Results) < int(pageSize) { + if dnslaListDomainsResp.Data == nil { break } - pageIndex++ + for _, domainItem := range dnslaListDomainsResp.Data.Results { + if strings.TrimRight(domainItem.Domain, ".") == zoneName || strings.TrimRight(domainItem.DisplayDomain, ".") == zoneName { + return domainItem, nil + } + } + + if len(dnslaListDomainsResp.Data.Results) < dnslaListDomainsPageSize { + break + } + + dnslaListDomainsPageIndex++ } - return nil, fmt.Errorf("zone '%s' not found", zoneName) + return nil, fmt.Errorf("could not find zone '%s'", zoneName) } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go index 13590f6e6..f46d39cd9 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/dynv6/internal/lego.go @@ -137,5 +137,5 @@ func (d *DNSProvider) findDNSRecord(zoneName, subDomain, tokenValue string) (lib } } - return nil, errors.New("record not found") + return nil, errors.New("could not find record") } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go b/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go index 95322aa6c..bbef553aa 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/jdcloud/internal/lego.go @@ -101,7 +101,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("jdcloud: %w", err) } - zone, err := d.getDNSZone(dns01.UnFqdn(authZone)) + zone, err := d.findZone(dns01.UnFqdn(authZone)) if err != nil { return fmt.Errorf("jdcloud: error when list zones: %w", err) } @@ -144,7 +144,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("jdcloud: unknown record ID for '%s'", info.EffectiveFQDN) } - zone, err := d.getDNSZone(dns01.UnFqdn(authZone)) + zone, err := d.findZone(dns01.UnFqdn(authZone)) if err != nil { return fmt.Errorf("jdcloud: error when list zones: %w", err) } @@ -166,15 +166,15 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getDNSZone(zoneName string) (*jddnsmodel.DomainInfo, error) { - pageNumber := 1 - pageSize := 10 +func (d *DNSProvider) findZone(zoneName string) (*jddnsmodel.DomainInfo, error) { + jddnsDescribeDomainsPageNumber := 1 + jddnsDescribeDomainsPageSize := 10 for { // REF: https://docs.jdcloud.com/cn/jd-cloud-dns/api/describedomains jddnsDescribeDomainsReq := jddns.NewDescribeDomainsRequestWithoutParam() jddnsDescribeDomainsReq.SetRegionId(d.config.RegionId) - jddnsDescribeDomainsReq.SetPageNumber(pageNumber) - jddnsDescribeDomainsReq.SetPageSize(pageSize) + jddnsDescribeDomainsReq.SetPageNumber(jddnsDescribeDomainsPageNumber) + jddnsDescribeDomainsReq.SetPageSize(jddnsDescribeDomainsPageSize) jddnsDescribeDomainsReq.SetDomainName(zoneName) jddnsDescribeDomainsResp, err := d.client.DescribeDomains(jddnsDescribeDomainsReq) @@ -182,18 +182,18 @@ func (d *DNSProvider) getDNSZone(zoneName string) (*jddnsmodel.DomainInfo, error return nil, err } - for _, item := range jddnsDescribeDomainsResp.Result.DataList { - if item.DomainName == zoneName { - return &item, nil + for _, domainItem := range jddnsDescribeDomainsResp.Result.DataList { + if domainItem.DomainName == zoneName { + return &domainItem, nil } } - if len(jddnsDescribeDomainsResp.Result.DataList) < pageSize { + if len(jddnsDescribeDomainsResp.Result.DataList) < jddnsDescribeDomainsPageSize { break } - pageNumber++ + jddnsDescribeDomainsPageNumber++ } - return nil, fmt.Errorf("jdcloud: zone %s not found", zoneName) + return nil, fmt.Errorf("could not find zone '%s'", zoneName) } diff --git a/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go index 96619421f..be7bc8d36 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go @@ -144,8 +144,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners listenerIds := make([]string, 0) - listListenersLimit := int32(100) - var listListenersToken *string = nil + listListenersToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -154,9 +153,9 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer } listListenersReq := &alialb.ListListenersRequest{ - MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, - LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + MaxResults: tea.Int32(100), + LoadBalancerIds: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("HTTPS"), } listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(context.TODO(), listListenersReq, &dara.RuntimeOptions{}) @@ -165,17 +164,19 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer return fmt.Errorf("failed to execute sdk request 'alb.ListListeners': %w", err) } - if listListenersResp.Body.Listeners != nil { - for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) - } + if listListenersResp.Body == nil { + break + } + + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil { break - } else { - listListenersToken = listListenersResp.Body.NextToken } + + listListenersToken = listListenersResp.Body.NextToken } // 查询 QUIC 监听列表 @@ -189,9 +190,9 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer } listListenersReq := &alialb.ListListenersRequest{ - MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, - LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + MaxResults: tea.Int32(100), + LoadBalancerIds: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("QUIC"), } listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(context.TODO(), listListenersReq, &dara.RuntimeOptions{}) @@ -200,17 +201,19 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer return fmt.Errorf("failed to execute sdk request 'alb.ListListeners': %w", err) } - if listListenersResp.Body.Listeners != nil { - for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) - } + if listListenersResp.Body == nil { + break + } + + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil { break - } else { - listListenersToken = listListenersResp.Body.NextToken } + + listListenersToken = listListenersResp.Body.NextToken } // 遍历更新监听证书 @@ -286,8 +289,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo // 查询监听证书列表 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlistenercertificates listenerCertificates := make([]alialb.ListListenerCertificatesResponseBodyCertificates, 0) - listListenerCertificatesLimit := int32(100) - var listListenerCertificatesToken *string = nil + listListenerCertificatesToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -297,7 +299,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo listListenerCertificatesReq := &alialb.ListListenerCertificatesRequest{ NextToken: listListenerCertificatesToken, - MaxResults: tea.Int32(listListenerCertificatesLimit), + MaxResults: tea.Int32(100), ListenerId: tea.String(cloudListenerId), CertificateType: tea.String("Server"), } @@ -307,17 +309,19 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo return fmt.Errorf("failed to execute sdk request 'alb.ListListenerCertificates': %w", err) } - if listListenerCertificatesResp.Body.Certificates != nil { - for _, listenerCertificate := range listListenerCertificatesResp.Body.Certificates { - listenerCertificates = append(listenerCertificates, *listenerCertificate) - } + if listListenerCertificatesResp.Body == nil { + break + } + + for _, listenerCertificate := range listListenerCertificatesResp.Body.Certificates { + listenerCertificates = append(listenerCertificates, *listenerCertificate) } if len(listListenerCertificatesResp.Body.Certificates) == 0 || listListenerCertificatesResp.Body.NextToken == nil { break - } else { - listListenerCertificatesToken = listListenerCertificatesResp.Body.NextToken } + + listListenerCertificatesToken = listListenerCertificatesResp.Body.NextToken } // 查询监听证书,并找出需要解除关联的证书 diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go index 0d3a3c158..06a31052e 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -146,52 +146,10 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s return errors.New("config `domain` is required") } - // 查询域名列表,获取域名 ID - // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains - var domainId string - listDomainsPageNumber := int32(1) - listDomainsPageSize := int32(10) - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - listDomainsReq := &aliapig.ListDomainsRequest{ - ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), - GatewayId: tea.String(d.config.GatewayId), - NameLike: tea.String(d.config.Domain), - PageNumber: tea.Int32(listDomainsPageNumber), - PageSize: tea.Int32(listDomainsPageSize), - } - listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomainsWithContext(context.TODO(), listDomainsReq, make(map[string]*string), &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'apig.ListDomains': %w", err) - } - - if listDomainsResp.Body.Data.Items != nil { - for _, domainInfo := range listDomainsResp.Body.Data.Items { - if strings.EqualFold(tea.StringValue(domainInfo.Name), d.config.Domain) { - domainId = tea.StringValue(domainInfo.DomainId) - break - } - } - - if domainId != "" { - break - } - } - - if listDomainsResp.Body.Data.Items == nil || len(listDomainsResp.Body.Data.Items) < int(listDomainsPageSize) { - break - } else { - listDomainsPageNumber++ - } - } - if domainId == "" { - return errors.New("domain not found") + // 获取域名 ID + domainId, err := d.findCloudNativeDomainIdByDomain(ctx, d.config.Domain) + if err != nil { + return err } // 查询域名 @@ -232,6 +190,51 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s return nil } +func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Context, domain string) (string, error) { + // 查询域名列表 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains + listDomainsPageNumber := 1 + listDomainsPageSize := 10 + for { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + listDomainsReq := &aliapig.ListDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + GatewayId: tea.String(d.config.GatewayId), + NameLike: tea.String(d.config.Domain), + PageNumber: tea.Int32(int32(listDomainsPageNumber)), + PageSize: tea.Int32(int32(listDomainsPageSize)), + } + listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomainsWithContext(context.TODO(), listDomainsReq, make(map[string]*string), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return "", fmt.Errorf("failed to execute sdk request 'apig.ListDomains': %w", err) + } + + if listDomainsResp.Body == nil || listDomainsResp.Body.Data == nil { + break + } + + for _, domainItem := range listDomainsResp.Body.Data.Items { + if strings.EqualFold(tea.StringValue(domainItem.Name), d.config.Domain) { + return tea.StringValue(domainItem.DomainId), nil + } + } + + if len(listDomainsResp.Body.Data.Items) < listDomainsPageSize { + break + } + + listDomainsPageNumber++ + } + + return "", fmt.Errorf("could not find domain '%s'", domain) +} + func createSDKClients(accessKeyId, accessKeySecret, region string) (*wSDKClients, error) { // 接入点一览 https://api.aliyun.com/product/APIG var cloudNativeAPIGEndpoint string diff --git a/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go b/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go index d016af23d..1967824b0 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go @@ -146,11 +146,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, fmt.Errorf("failed to execute sdk request 'cas.DescribeDeploymentJob': %w", err) } - if describeDeploymentJobResp.Body.Status == nil || *describeDeploymentJobResp.Body.Status == "editing" { + status := tea.StringValue(describeDeploymentJobResp.Body.Status) + if status == "" || status == "editing" { return nil, errors.New("unexpected aliyun deployment job status") - } - - if *describeDeploymentJobResp.Body.Status == "success" || *describeDeploymentJobResp.Body.Status == "error" { + } else if status == "success" || status == "error" { break } 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 d5bc06697..74a0366c0 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -187,8 +187,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro // 查询域名列表 // REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-describeuserdomains - describeUserDomainsPageNumber := int32(1) - describeUserDomainsPageSize := int32(500) + describeUserDomainsPageNumber := 1 + describeUserDomainsPageSize := 500 for { select { case <-ctx.Done(): @@ -198,8 +198,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro describeUserDomainsReq := &alicdn.DescribeUserDomainsRequest{ ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), - PageNumber: tea.Int32(describeUserDomainsPageNumber), - PageSize: tea.Int32(describeUserDomainsPageSize), + PageNumber: tea.Int32(int32(describeUserDomainsPageNumber)), + PageSize: tea.Int32(int32(describeUserDomainsPageSize)), } describeUserDomainsResp, err := d.sdkClient.DescribeUserDomainsWithContext(ctx, describeUserDomainsReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cdn.DescribeUserDomains'", slog.Any("request", describeUserDomainsReq), slog.Any("response", describeUserDomainsResp)) @@ -207,22 +207,24 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeUserDomains': %w", err) } - if describeUserDomainsResp.Body.Domains != nil { - ignoredStatuses := []string{"offline", "checking", "check_failed", "stopping", "deleting"} - for _, domainInfo := range describeUserDomainsResp.Body.Domains.PageData { - if lo.Contains(ignoredStatuses, tea.StringValue(domainInfo.DomainStatus)) { - continue - } - - domains = append(domains, tea.StringValue(domainInfo.DomainName)) - } - } - - if len(describeUserDomainsResp.Body.Domains.PageData) < int(describeUserDomainsPageNumber) { + if describeUserDomainsResp.Body == nil || describeUserDomainsResp.Body.Domains == nil { break - } else { - describeUserDomainsPageNumber++ } + + ignoredStatuses := []string{"offline", "checking", "check_failed", "stopping", "deleting"} + for _, domainItem := range describeUserDomainsResp.Body.Domains.PageData { + if lo.Contains(ignoredStatuses, tea.StringValue(domainItem.DomainStatus)) { + continue + } + + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(describeUserDomainsResp.Body.Domains.PageData) < describeUserDomainsPageSize { + break + } + + describeUserDomainsPageNumber++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go b/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go index 79939fbc6..29d54df4d 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go @@ -132,8 +132,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerlisteners listenerPorts := make([]int32, 0) - describeLoadBalancerListenersLimit := int32(100) - var describeLoadBalancerListenersToken *string = nil + describeLoadBalancerListenersToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -143,9 +142,9 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer describeLoadBalancerListenersReq := &alislb.DescribeLoadBalancerListenersRequest{ RegionId: tea.String(d.config.Region), - MaxResults: tea.Int32(describeLoadBalancerListenersLimit), NextToken: describeLoadBalancerListenersToken, - LoadBalancerId: []*string{tea.String(d.config.LoadbalancerId)}, + MaxResults: tea.Int32(100), + LoadBalancerId: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("https"), } describeLoadBalancerListenersResp, err := d.sdkClient.DescribeLoadBalancerListeners(describeLoadBalancerListenersReq) @@ -154,17 +153,19 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer return fmt.Errorf("failed to execute sdk request 'slb.DescribeLoadBalancerListeners': %w", err) } - if describeLoadBalancerListenersResp.Body.Listeners != nil { - for _, listener := range describeLoadBalancerListenersResp.Body.Listeners { - listenerPorts = append(listenerPorts, *listener.ListenerPort) - } + if describeLoadBalancerListenersResp.Body == nil { + break + } + + for _, listener := range describeLoadBalancerListenersResp.Body.Listeners { + listenerPorts = append(listenerPorts, *listener.ListenerPort) } if len(describeLoadBalancerListenersResp.Body.Listeners) == 0 || describeLoadBalancerListenersResp.Body.NextToken == nil { break - } else { - describeLoadBalancerListenersToken = describeLoadBalancerListenersResp.Body.NextToken } + + describeLoadBalancerListenersToken = describeLoadBalancerListenersResp.Body.NextToken } // 遍历更新监听证书 diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index f703619d5..4a22b6112 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -187,8 +187,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro // 查询域名列表 // REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-describedcdnuserdomains - describeUserDomainsPageNumber := int32(1) - describeUserDomainsPageSize := int32(500) + describeUserDomainsPageNumber := 1 + describeUserDomainsPageSize := 500 for { select { case <-ctx.Done(): @@ -199,8 +199,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro describeDcdnUserDomainsReq := &alidcdn.DescribeDcdnUserDomainsRequest{ ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), CheckDomainShow: tea.Bool(true), - PageNumber: tea.Int32(describeUserDomainsPageNumber), - PageSize: tea.Int32(describeUserDomainsPageSize), + PageNumber: tea.Int32(int32(describeUserDomainsPageNumber)), + PageSize: tea.Int32(int32(describeUserDomainsPageSize)), } describeDcdnUserDomainsResp, err := d.sdkClient.DescribeDcdnUserDomainsWithContext(ctx, describeDcdnUserDomainsReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'dcdn.DescribeDcdnUserDomains'", slog.Any("request", describeDcdnUserDomainsReq), slog.Any("response", describeDcdnUserDomainsResp)) @@ -208,20 +208,24 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'dcdn.DescribeDcdnUserDomains': %w", err) } + if describeDcdnUserDomainsResp.Body == nil || describeDcdnUserDomainsResp.Body.Domains == nil { + break + } + ignoredStatuses := []string{"offline", "checking", "check_failed", "stopping", "deleting"} - for _, domainInfo := range describeDcdnUserDomainsResp.Body.Domains.PageData { - if lo.Contains(ignoredStatuses, tea.StringValue(domainInfo.DomainStatus)) { + for _, domainItem := range describeDcdnUserDomainsResp.Body.Domains.PageData { + if lo.Contains(ignoredStatuses, tea.StringValue(domainItem.DomainStatus)) { continue } - domains = append(domains, tea.StringValue(domainInfo.DomainName)) + domains = append(domains, tea.StringValue(domainItem.DomainName)) } - if len(describeDcdnUserDomainsResp.Body.Domains.PageData) < int(describeUserDomainsPageNumber) { + if len(describeDcdnUserDomainsResp.Body.Domains.PageData) < describeUserDomainsPageNumber { break - } else { - describeUserDomainsPageNumber++ } + + describeUserDomainsPageNumber++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/aliyun-ga/aliyun_ga.go b/pkg/core/ssl-deployer/providers/aliyun-ga/aliyun_ga.go index 8f0f5bd19..0eec25028 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ga/aliyun_ga.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ga/aliyun_ga.go @@ -119,8 +119,8 @@ func (d *SSLDeployerProvider) deployToAccelerator(ctx context.Context, cloudCert // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlisteners listenerIds := make([]string, 0) - listListenersPageNumber := int32(1) - listListenersPageSize := int32(50) + listListenersPageNumber := 1 + listListenersPageSize := 50 for { select { case <-ctx.Done(): @@ -131,8 +131,8 @@ func (d *SSLDeployerProvider) deployToAccelerator(ctx context.Context, cloudCert listListenersReq := &aliga.ListListenersRequest{ RegionId: tea.String("cn-hangzhou"), AcceleratorId: tea.String(d.config.AcceleratorId), - PageNumber: tea.Int32(listListenersPageNumber), - PageSize: tea.Int32(listListenersPageSize), + PageNumber: tea.Int32(int32(listListenersPageNumber)), + PageSize: tea.Int32(int32(listListenersPageSize)), } listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) d.logger.Debug("sdk request 'ga.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) @@ -140,19 +140,21 @@ func (d *SSLDeployerProvider) deployToAccelerator(ctx context.Context, cloudCert return fmt.Errorf("failed to execute sdk request 'ga.ListListeners': %w", err) } - if listListenersResp.Body.Listeners != nil { - for _, listener := range listListenersResp.Body.Listeners { - if strings.EqualFold(tea.StringValue(listener.Protocol), "https") { - listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) - } + if listListenersResp.Body == nil { + break + } + + for _, listener := range listListenersResp.Body.Listeners { + if strings.EqualFold(tea.StringValue(listener.Protocol), "https") { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } } - if len(listListenersResp.Body.Listeners) < int(listListenersPageSize) { + if len(listListenersResp.Body.Listeners) < listListenersPageSize { break - } else { - listListenersPageNumber++ } + + listListenersPageNumber++ } // 遍历更新监听证书 @@ -200,9 +202,9 @@ func (d *SSLDeployerProvider) deployToListener(ctx context.Context, cloudCertId func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, cloudAcceleratorId string, cloudListenerId string, cloudCertId string) error { // 查询监听绑定的证书列表 // REF: https://help.aliyun.com/zh/ga/developer-reference/api-ga-2019-11-20-listlistenercertificates - var listenerDefaultCertificate *aliga.ListListenerCertificatesResponseBodyCertificates - var listenerAdditionalCertificates []*aliga.ListListenerCertificatesResponseBodyCertificates = make([]*aliga.ListListenerCertificatesResponseBodyCertificates, 0) - var listListenerCertificatesNextToken *string + listenerDefaultCertificate := (*aliga.ListListenerCertificatesResponseBodyCertificates)(nil) + listenerAdditionalCertificates := make([]*aliga.ListListenerCertificatesResponseBodyCertificates, 0) + listListenerCertificatesNextToken := (*string)(nil) for { listListenerCertificatesReq := &aliga.ListListenerCertificatesRequest{ RegionId: tea.String("cn-hangzhou"), @@ -217,21 +219,23 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo return fmt.Errorf("failed to execute sdk request 'ga.ListListenerCertificates': %w", err) } - if listListenerCertificatesResp.Body.Certificates != nil { - for _, certificate := range listListenerCertificatesResp.Body.Certificates { - if tea.BoolValue(certificate.IsDefault) { - listenerDefaultCertificate = certificate - } else { - listenerAdditionalCertificates = append(listenerAdditionalCertificates, certificate) - } + if listListenerCertificatesResp.Body == nil { + break + } + + for _, certItem := range listListenerCertificatesResp.Body.Certificates { + if tea.BoolValue(certItem.IsDefault) { + listenerDefaultCertificate = certItem + } else { + listenerAdditionalCertificates = append(listenerAdditionalCertificates, certItem) } } - if listListenerCertificatesResp.Body.NextToken == nil { + if len(listListenerCertificatesResp.Body.Certificates) == 0 || listListenerCertificatesResp.Body.NextToken == nil { break - } else { - listListenerCertificatesNextToken = listListenerCertificatesResp.Body.NextToken } + + listListenerCertificatesNextToken = listListenerCertificatesResp.Body.NextToken } if d.config.Domain == "" { diff --git a/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go b/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go index f51f7b8ee..55ff9625e 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -133,8 +133,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 TCPSSL 监听列表 // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners listenerIds := make([]string, 0) - listListenersLimit := int32(100) - var listListenersToken *string = nil + listListenersToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -143,9 +142,9 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer } listListenersReq := &alinlb.ListListenersRequest{ - MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, - LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + MaxResults: tea.Int32(100), + LoadBalancerIds: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("TCPSSL"), } listListenersResp, err := d.sdkClient.ListListenersWithContext(context.TODO(), listListenersReq, &dara.RuntimeOptions{}) @@ -154,17 +153,19 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer return fmt.Errorf("failed to execute sdk request 'nlb.ListListeners': %w", err) } - if listListenersResp.Body.Listeners != nil { - for _, listener := range listListenersResp.Body.Listeners { - listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) - } + if listListenersResp.Body == nil { + break + } + + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, tea.StringValue(listener.ListenerId)) } if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil { break - } else { - listListenersToken = listListenersResp.Body.NextToken } + + listListenersToken = listListenersResp.Body.NextToken } // 遍历更新监听证书 diff --git a/pkg/core/ssl-deployer/providers/baiducloud-appblb/baiducloud_appblb.go b/pkg/core/ssl-deployer/providers/baiducloud-appblb/baiducloud_appblb.go index e95034cef..4c5847057 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-appblb/baiducloud_appblb.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-appblb/baiducloud_appblb.go @@ -255,7 +255,7 @@ func (d *SSLDeployerProvider) updateHttpsListenerCertificate(ctx context.Context if err != nil { return fmt.Errorf("failed to execute sdk request 'appblb.DescribeAppHTTPSListeners': %w", err) } else if len(describeAppHTTPSListenersResp.ListenerList) == 0 { - return fmt.Errorf("listener %s:%d not found", cloudLoadbalancerId, cloudHttpsListenerPort) + return fmt.Errorf("cloud not find listener '%s:%d'", cloudLoadbalancerId, cloudHttpsListenerPort) } if d.config.Domain == "" { diff --git a/pkg/core/ssl-deployer/providers/baiducloud-blb/baiducloud_blb.go b/pkg/core/ssl-deployer/providers/baiducloud-blb/baiducloud_blb.go index e6fd15239..55d11ef5d 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-blb/baiducloud_blb.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-blb/baiducloud_blb.go @@ -255,7 +255,7 @@ func (d *SSLDeployerProvider) updateHttpsListenerCertificate(ctx context.Context if err != nil { return fmt.Errorf("failed to execute sdk request 'blb.DescribeHTTPSListeners': %w", err) } else if len(describeHTTPSListenersResp.ListenerList) == 0 { - return fmt.Errorf("listener %s:%d not found", cloudLoadbalancerId, cloudHttpsListenerPort) + return fmt.Errorf("could not find listener '%s:%d'", cloudLoadbalancerId, cloudHttpsListenerPort) } if 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 75100ecac..42416da7c 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go @@ -171,9 +171,9 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro if listDomainsNextMarker == "" { break - } else { - listDomainsMarker = listDomainsNextMarker } + + listDomainsMarker = listDomainsNextMarker } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn.go b/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn.go index 7ded574b7..7db318aab 100644 --- a/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn.go +++ b/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn.go @@ -19,6 +19,9 @@ import ( type SSLDeployerProviderConfig struct { // 白山云 API Token。 ApiToken string `json:"apiToken"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` // 证书 ID。 @@ -103,7 +106,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'baishan.GetDomainConfig': %w", err) } else if len(getDomainConfigResp.Data) == 0 { - return nil, fmt.Errorf("domain %s not found", d.config.Domain) + return nil, fmt.Errorf("could not find domain '%s'", d.config.Domain) } // 设置域名配置 diff --git a/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn_test.go b/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn_test.go index e989785c0..b9aad6b89 100644 --- a/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/baishan-cdn/baishan_cdn_test.go @@ -49,8 +49,9 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - ApiToken: fApiToken, - Domain: fDomain, + ApiToken: fApiToken, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/baishan-cdn/consts.go b/pkg/core/ssl-deployer/providers/baishan-cdn/consts.go new file mode 100644 index 000000000..7b86619c4 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/baishan-cdn/consts.go @@ -0,0 +1,6 @@ +package baishancdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go b/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go index 9b925b0a6..27a32ee61 100644 --- a/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go +++ b/pkg/core/ssl-deployer/providers/baotapanelgo-site/baotapanelgo_site.go @@ -74,48 +74,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, fmt.Errorf("failed to execute sdk request 'bt.PanelGetConfig': %w", err) } - // 查询网站列表,获取网站 ID - var siteId int32 - datalistGetDataListPage := int32(1) - datalistGetDataListLimit := int32(10) - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - datalistGetDataListReq := &btsdk.DatalistGetDataListRequest{ - Table: lo.ToPtr("sites"), - SearchString: lo.ToPtr(d.config.SiteName), - Page: lo.ToPtr(datalistGetDataListPage), - Limit: lo.ToPtr(datalistGetDataListLimit), - } - datalistGetDataListResp, err := d.sdkClient.DatalistGetDataList(datalistGetDataListReq) - d.logger.Debug("sdk request 'bt.DatalistGetDataList'", slog.Any("request", datalistGetDataListReq), slog.Any("response", datalistGetDataListResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'bt.DatalistGetDataList': %w", err) - } - - for _, siteInfo := range datalistGetDataListResp.Data { - if strings.EqualFold(siteInfo.Name, d.config.SiteName) { - siteId = siteInfo.Id - break - } - } - - if siteId != 0 { - break - } - - if len(datalistGetDataListResp.Data) < int(datalistGetDataListLimit) { - break - } else { - datalistGetDataListPage++ - } - } - if siteId == 0 { - return nil, errors.New("website not found") + // 获取网站 ID + siteId, err := d.findSiteIdByName(ctx, d.config.SiteName) + if err != nil { + return nil, err } if panelGetConfigResp.Site != nil && strings.EqualFold(panelGetConfigResp.Site.WebServer, "iis") { @@ -174,6 +136,45 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) findSiteIdByName(ctx context.Context, siteName string) (int32, error) { + // 查询网站列表 + datalistGetDataListPage := 1 + datalistGetDataListLimit := 10 + for { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + + datalistGetDataListReq := &btsdk.DatalistGetDataListRequest{ + Table: lo.ToPtr("sites"), + SearchString: lo.ToPtr(d.config.SiteName), + Page: lo.ToPtr(int32(datalistGetDataListPage)), + Limit: lo.ToPtr(int32(datalistGetDataListLimit)), + } + datalistGetDataListResp, err := d.sdkClient.DatalistGetDataList(datalistGetDataListReq) + d.logger.Debug("sdk request 'bt.DatalistGetDataList'", slog.Any("request", datalistGetDataListReq), slog.Any("response", datalistGetDataListResp)) + if err != nil { + return 0, fmt.Errorf("failed to execute sdk request 'bt.DatalistGetDataList': %w", err) + } + + for _, siteItem := range datalistGetDataListResp.Data { + if strings.EqualFold(siteItem.Name, d.config.SiteName) { + return siteItem.Id, nil + } + } + + if len(datalistGetDataListResp.Data) < datalistGetDataListLimit { + break + } + + datalistGetDataListPage++ + } + + return 0, fmt.Errorf("could not find site '%s'", siteName) +} + func createSDKClient(serverUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) { client, err := btsdk.NewClient(serverUrl, apiKey) if err != nil { diff --git a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go index 1ea80534e..e5a9dc72a 100644 --- a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go +++ b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go @@ -68,46 +68,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.config.SitePort = 443 } - // 查询网站列表,获取网站 ID - // REF: https://support.huaweicloud.com/api-waf/ListHost.html - siteId := "" - getSitListPage := int32(1) - getSitListPageSize := int32(100) - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - getSiteListReq := &btwafsdk.GetSiteListRequest{ - SiteName: lo.ToPtr(d.config.SiteName), - Page: lo.ToPtr(getSitListPage), - PageSize: lo.ToPtr(getSitListPageSize), - } - getSiteListResp, err := d.sdkClient.GetSiteList(getSiteListReq) - d.logger.Debug("sdk request 'bt.GetSiteList'", slog.Any("request", getSiteListReq), slog.Any("response", getSiteListResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'bt.GetSiteList': %w", err) - } - - if getSiteListResp.Result != nil && getSiteListResp.Result.List != nil { - for _, siteItem := range getSiteListResp.Result.List { - if siteItem.SiteName == d.config.SiteName { - siteId = siteItem.SiteId - break - } - } - } - - if getSiteListResp.Result == nil || len(getSiteListResp.Result.List) < int(getSitListPageSize) { - break - } else { - getSitListPage++ - } - } - if siteId == "" { - return nil, errors.New("site not found") + // 获取网站 ID + siteId, err := d.findSiteIdByName(ctx, d.config.SiteName) + if err != nil { + return nil, err } // 修改站点配置 @@ -132,6 +96,48 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) findSiteIdByName(ctx context.Context, siteName string) (string, error) { + // 查询网站列表 + getSiteListPage := 1 + getSiteListPageSize := 100 + for { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + getSiteListReq := &btwafsdk.GetSiteListRequest{ + SiteName: lo.ToPtr(d.config.SiteName), + Page: lo.ToPtr(int32(getSiteListPage)), + PageSize: lo.ToPtr(int32(getSiteListPageSize)), + } + getSiteListResp, err := d.sdkClient.GetSiteList(getSiteListReq) + d.logger.Debug("sdk request 'bt.GetSiteList'", slog.Any("request", getSiteListReq), slog.Any("response", getSiteListResp)) + if err != nil { + return "", fmt.Errorf("failed to execute sdk request 'bt.GetSiteList': %w", err) + } + + if getSiteListResp.Result == nil { + break + } + + for _, siteItem := range getSiteListResp.Result.List { + if siteItem.SiteName == d.config.SiteName { + return siteItem.SiteId, nil + } + } + + if len(getSiteListResp.Result.List) < getSiteListPageSize { + break + } + + getSiteListPage++ + } + + return "", fmt.Errorf("could not find site '%s'", siteName) +} + func createSDKClient(serverUrl, apiKey string, skipTlsVerify bool) (*btwafsdk.Client, error) { client, err := btwafsdk.NewClient(serverUrl, apiKey) if err != nil { diff --git a/pkg/core/ssl-deployer/providers/bunny-cdn/bunny_cdn.go b/pkg/core/ssl-deployer/providers/bunny-cdn/bunny_cdn.go index a86803e40..0f03b7825 100644 --- a/pkg/core/ssl-deployer/providers/bunny-cdn/bunny_cdn.go +++ b/pkg/core/ssl-deployer/providers/bunny-cdn/bunny_cdn.go @@ -16,7 +16,7 @@ type SSLDeployerProviderConfig struct { ApiKey string `json:"apiKey"` // Bunny Pull Zone ID。 PullZoneId string `json:"pullZoneId"` - // Bunny CDN Hostname(支持泛域名)。 + // Bunny CDN Hostname。 Hostname string `json:"hostname"` } 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 1ace82c0e..40a54380d 100644 --- a/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go +++ b/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -155,8 +155,8 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w // 查询加速域名列表,获取匹配的域名 // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/ListCdnDomains_en-us - listCdnDomainsPageNum := int64(1) - listCdnDomainsPageSize := int64(100) + listCdnDomainsPageNum := 1 + listCdnDomainsPageSize := 100 for { select { case <-ctx.Done(): @@ -167,8 +167,8 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w listCdnDomainsReq := &bpcdn.ListCdnDomainsRequest{ Domain: bp.String(strings.TrimPrefix(wildcardDomain, "*.")), Status: bp.String("online"), - PageNum: bp.Int64(listCdnDomainsPageNum), - PageSize: bp.Int64(listCdnDomainsPageSize), + PageNum: bp.Int64(int64(listCdnDomainsPageNum)), + PageSize: bp.Int64(int64(listCdnDomainsPageSize)), } listCdnDomainsResp, err := d.sdkClient.ListCdnDomains(listCdnDomainsReq) d.logger.Debug("sdk request 'cdn.ListCdnDomains'", slog.Any("request", listCdnDomainsReq), slog.Any("response", listCdnDomainsResp)) @@ -176,17 +176,17 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w 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) + for _, domainItem := range listCdnDomainsResp.Result.Data { + if xcerthostname.IsMatch(wildcardDomain, domainItem.Domain) { + domains = append(domains, domainItem.Domain) } } - if len(listCdnDomainsResp.Result.Data) < int(listCdnDomainsPageSize) { + if len(listCdnDomainsResp.Result.Data) < listCdnDomainsPageSize { break - } else { - listCdnDomainsPageSize++ } + + listCdnDomainsPageSize++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go index 591a63576..dfe672fc2 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go @@ -171,8 +171,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro // 查询域名列表 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13816&data=174&isNormal=1&vid=167 - queryDomainsPage := int32(1) - queryDomainsPageSize := int32(100) + queryDomainsPage := 1 + queryDomainsPageSize := 100 for { select { case <-ctx.Done(): @@ -181,8 +181,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } queryDomainsReq := &ctyunao.QueryDomainsRequest{ - Page: lo.ToPtr(queryDomainsPage), - PageSize: lo.ToPtr(queryDomainsPageSize), + Page: lo.ToPtr(int32(queryDomainsPage)), + PageSize: lo.ToPtr(int32(queryDomainsPageSize)), ProductCode: lo.ToPtr("020"), } queryDomainsResp, err := d.sdkClient.QueryDomains(queryDomainsReq) @@ -191,22 +191,24 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomains': %w", err) } - if queryDomainsResp.ReturnObj != nil { - ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} - for _, domainInfo := range queryDomainsResp.ReturnObj.Results { - if lo.Contains(ignoredStatuses, domainInfo.Status) { - continue - } - - domains = append(domains, domainInfo.Domain) - } - } - - if queryDomainsResp.ReturnObj == nil || len(queryDomainsResp.ReturnObj.Results) < int(queryDomainsPageSize) { + if queryDomainsResp.ReturnObj == nil { break - } else { - queryDomainsPage++ } + + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainItem := range queryDomainsResp.ReturnObj.Results { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Domain) + } + + if len(queryDomainsResp.ReturnObj.Results) < queryDomainsPageSize { + break + } + + queryDomainsPage++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go index b21777b15..e4603ba4b 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go @@ -170,8 +170,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro // 查询域名列表 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11307&data=161&isNormal=1&vid=154 - queryDomainListPage := int32(1) - queryDomainListPageSize := int32(100) + queryDomainListPage := 1 + queryDomainListPageSize := 100 for { select { case <-ctx.Done(): @@ -180,8 +180,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } queryDomainListReq := &ctyuncdn.QueryDomainListRequest{ - Page: lo.ToPtr(queryDomainListPage), - PageSize: lo.ToPtr(queryDomainListPageSize), + Page: lo.ToPtr(int32(queryDomainListPage)), + PageSize: lo.ToPtr(int32(queryDomainListPageSize)), ProductCode: lo.ToPtr("020"), } queryDomainListResp, err := d.sdkClient.QueryDomainList(queryDomainListReq) @@ -190,26 +190,28 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainList': %w", err) } - if queryDomainListResp.ReturnObj != nil { - filteredProductCodes := []string{"001", "003", "004", "008"} - ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} - for _, domainInfo := range queryDomainListResp.ReturnObj.Results { - if !lo.Contains(filteredProductCodes, domainInfo.ProductCode) { - continue - } - if lo.Contains(ignoredStatuses, domainInfo.Status) { - continue - } - - domains = append(domains, domainInfo.Domain) - } - } - - if queryDomainListResp.ReturnObj == nil || len(queryDomainListResp.ReturnObj.Results) < int(queryDomainListPageSize) { + if queryDomainListResp.ReturnObj == nil { break - } else { - queryDomainListPage++ } + + filteredProductCodes := []string{"001", "003", "004", "008"} + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainItem := range queryDomainListResp.ReturnObj.Results { + if !lo.Contains(filteredProductCodes, domainItem.ProductCode) { + continue + } + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Domain) + } + + if len(queryDomainListResp.ReturnObj.Results) < queryDomainListPageSize { + break + } + + queryDomainListPage++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-elb/ctcccloud_elb.go b/pkg/core/ssl-deployer/providers/ctcccloud-elb/ctcccloud_elb.go index 805255cbb..380af98be 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-elb/ctcccloud_elb.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-elb/ctcccloud_elb.go @@ -111,13 +111,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询监听列表 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=24&api=5654&data=88&isNormal=1&vid=82 listenerIds := make([]string, 0) - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - + { listListenersReq := &ctyunelb.ListListenersRequest{ RegionID: lo.ToPtr(d.config.RegionId), LoadBalancerID: lo.ToPtr(d.config.LoadbalancerId), @@ -133,8 +127,6 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer listenerIds = append(listenerIds, listener.ID) } } - - break } // 遍历更新监听证书 diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go index 7a3a2c525..0760693e1 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go @@ -170,8 +170,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro // 查询域名列表 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10852&data=173&isNormal=1&vid=166 - queryDomainsPage := int32(1) - queryDomainsPageSize := int32(100) + queryDomainsPage := 1 + queryDomainsPageSize := 100 for { select { case <-ctx.Done(): @@ -180,8 +180,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } queryDomainListReq := &ctyunicdn.QueryDomainListRequest{ - Page: lo.ToPtr(queryDomainsPage), - PageSize: lo.ToPtr(queryDomainsPageSize), + Page: lo.ToPtr(int32(queryDomainsPage)), + PageSize: lo.ToPtr(int32(queryDomainsPageSize)), ProductCode: lo.ToPtr("006"), } queryDomainListResp, err := d.sdkClient.QueryDomainList(queryDomainListReq) @@ -190,22 +190,24 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainList': %w", err) } - if queryDomainListResp.ReturnObj != nil { - ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} - for _, domainInfo := range queryDomainListResp.ReturnObj.Results { - if lo.Contains(ignoredStatuses, domainInfo.Status) { - continue - } - - domains = append(domains, domainInfo.Domain) - } - } - - if queryDomainListResp.ReturnObj == nil || len(queryDomainListResp.ReturnObj.Results) < int(queryDomainsPageSize) { + if queryDomainListResp.ReturnObj == nil { break - } else { - queryDomainsPage++ } + + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainItem := range queryDomainListResp.ReturnObj.Results { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Domain) + } + + if len(queryDomainListResp.ReturnObj.Results) < queryDomainsPageSize { + break + } + + queryDomainsPage++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index 4e481c544..b71a6135e 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -185,8 +185,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro // 查询域名列表 // REF: https://support.huaweicloud.com/api-cdn/ListDomains.html - listDomainsPageNumber := int32(1) - listDomainsPageSize := int32(100) + listDomainsPageNumber := 1 + listDomainsPageSize := 100 for { select { case <-ctx.Done(): @@ -196,8 +196,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro listDomainsReq := &hccdnmodel.ListDomainsRequest{ EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), - PageNumber: lo.ToPtr(listDomainsPageNumber), - PageSize: lo.ToPtr(listDomainsPageSize), + PageNumber: lo.ToPtr(int32(listDomainsPageNumber)), + PageSize: lo.ToPtr(int32(listDomainsPageSize)), } listDomainsResp, err := d.sdkClient.ListDomains(listDomainsReq) d.logger.Debug("sdk request 'cdn.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) @@ -205,22 +205,24 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListDomains': %w", err) } - if listDomainsResp.Domains != nil { - ignoredStatuses := []string{"offline", "checking", "check_failed", "deleting"} - for _, domainInfo := range *listDomainsResp.Domains { - if lo.Contains(ignoredStatuses, lo.FromPtr(domainInfo.DomainStatus)) { - continue - } - - domains = append(domains, lo.FromPtr(domainInfo.DomainName)) - } - } - - if listDomainsResp.Domains == nil || len(*listDomainsResp.Domains) < int(listDomainsPageSize) { + if listDomainsResp.Domains == nil { break - } else { - listDomainsPageNumber++ } + + ignoredStatuses := []string{"offline", "checking", "check_failed", "deleting"} + for _, domainItem := range *listDomainsResp.Domains { + if lo.Contains(ignoredStatuses, lo.FromPtr(domainItem.DomainStatus)) { + continue + } + + domains = append(domains, lo.FromPtr(domainItem.DomainName)) + } + + if len(*listDomainsResp.Domains) < listDomainsPageSize { + break + } + + listDomainsPageNumber++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go index 2801de666..93b37448c 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -160,8 +160,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM // 查询监听器列表 // REF: https://support.huaweicloud.com/api-elb/ListListeners.html listenerIds := make([]string, 0) - listListenersLimit := int32(2000) - var listListenersMarker *string = nil + listListenersMarker := (*string)(nil) for { select { case <-ctx.Done(): @@ -170,8 +169,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM } listListenersReq := &hcelbmodel.ListListenersRequest{ - Limit: lo.ToPtr(listListenersLimit), Marker: listListenersMarker, + Limit: lo.ToPtr(int32(2000)), Protocol: &[]string{"HTTPS", "TERMINATED_HTTPS"}, LoadbalancerId: &[]string{showLoadBalancerResp.Loadbalancer.Id}, } @@ -184,17 +183,19 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM return fmt.Errorf("failed to execute sdk request 'elb.ListListeners': %w", err) } - if listListenersResp.Listeners != nil { - for _, listener := range *listListenersResp.Listeners { - listenerIds = append(listenerIds, listener.Id) - } + if listListenersResp.Listeners == nil { + break } - if listListenersResp.Listeners == nil || len(*listListenersResp.Listeners) < int(listListenersLimit) { - break - } else { - listListenersMarker = listListenersResp.PageInfo.NextMarker + for _, listener := range *listListenersResp.Listeners { + listenerIds = append(listenerIds, listener.Id) } + + if len(*listListenersResp.Listeners) == 0 || listListenersResp.PageInfo.NextMarker == nil { + break + } + + listListenersMarker = listListenersResp.PageInfo.NextMarker } // 上传证书 diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go b/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go index dacce0bea..38f13e09f 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-waf/huaweicloud_waf.go @@ -174,8 +174,8 @@ func (d *SSLDeployerProvider) deployToCloudServer(ctx context.Context, certPEM s // 查询云模式防护域名列表,获取防护域名 ID // REF: https://support.huaweicloud.com/api-waf/ListHost.html hostId := "" - listHostPage := int32(1) - listHostPageSize := int32(100) + listHostPage := 1 + listHostPageSize := 100 for { select { case <-ctx.Done(): @@ -186,8 +186,8 @@ func (d *SSLDeployerProvider) deployToCloudServer(ctx context.Context, certPEM s listHostReq := &hcwafmodel.ListHostRequest{ EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), Hostname: lo.ToPtr(strings.TrimPrefix(d.config.Domain, "*")), - Page: lo.ToPtr(listHostPage), - Pagesize: lo.ToPtr(listHostPageSize), + Page: lo.ToPtr(int32(listHostPage)), + Pagesize: lo.ToPtr(int32(listHostPageSize)), } listHostResp, err := d.sdkClient.ListHost(listHostReq) d.logger.Debug("sdk request 'waf.ListHost'", slog.Any("request", listHostReq), slog.Any("response", listHostResp)) @@ -195,23 +195,25 @@ func (d *SSLDeployerProvider) deployToCloudServer(ctx context.Context, certPEM s return fmt.Errorf("failed to execute sdk request 'waf.ListHost': %w", err) } - if listHostResp.Items != nil { - for _, hostItem := range *listHostResp.Items { - if strings.TrimPrefix(d.config.Domain, "*") == *hostItem.Hostname { - hostId = *hostItem.Id - break - } + if listHostResp.Items == nil { + break + } + + for _, hostItem := range *listHostResp.Items { + if strings.TrimPrefix(d.config.Domain, "*") == *hostItem.Hostname { + hostId = *hostItem.Id + break } } - if listHostResp.Items == nil || len(*listHostResp.Items) < int(listHostPageSize) { + if len(*listHostResp.Items) < listHostPageSize { break - } else { - listHostPage++ } + + listHostPage++ } if hostId == "" { - return errors.New("host not found") + return fmt.Errorf("could not find cloudserver host '%s'", d.config.Domain) } // 更新云模式防护域名的配置 @@ -249,8 +251,8 @@ func (d *SSLDeployerProvider) deployToPremiumHost(ctx context.Context, certPEM s // 查询独享模式域名列表,获取防护域名 ID // REF: https://support.huaweicloud.com/api-waf/ListPremiumHost.html hostId := "" - listPremiumHostPage := int32(1) - listPremiumHostPageSize := int32(100) + listPremiumHostPage := 1 + listPremiumHostPageSize := 100 for { select { case <-ctx.Done(): @@ -270,23 +272,25 @@ func (d *SSLDeployerProvider) deployToPremiumHost(ctx context.Context, certPEM s return fmt.Errorf("failed to execute sdk request 'waf.ListPremiumHost': %w", err) } - if listPremiumHostResp.Items != nil { - for _, hostItem := range *listPremiumHostResp.Items { - if strings.TrimPrefix(d.config.Domain, "*") == *hostItem.Hostname { - hostId = *hostItem.Id - break - } + if listPremiumHostResp.Items == nil { + break + } + + for _, hostItem := range *listPremiumHostResp.Items { + if strings.TrimPrefix(d.config.Domain, "*") == *hostItem.Hostname { + hostId = *hostItem.Id + break } } - if listPremiumHostResp.Items == nil || len(*listPremiumHostResp.Items) < int(listPremiumHostPageSize) { + if len(*listPremiumHostResp.Items) < listPremiumHostPageSize { break - } else { - listPremiumHostPage++ } + + listPremiumHostPage++ } if hostId == "" { - return errors.New("host not found") + return fmt.Errorf("could not find premium host '%s'", d.config.Domain) } // 修改独享模式域名配置 diff --git a/pkg/core/ssl-deployer/providers/jdcloud-alb/jdcloud_alb.go b/pkg/core/ssl-deployer/providers/jdcloud-alb/jdcloud_alb.go index b69d2cf86..c4e7f534d 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-alb/jdcloud_alb.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-alb/jdcloud_alb.go @@ -156,11 +156,11 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer } } - if len(describeListenersResp.Result.Listeners) < int(describeListenersPageSize) { + if len(describeListenersResp.Result.Listeners) < describeListenersPageSize { break - } else { - describeListenersPageNumber++ } + + describeListenersPageNumber++ } // 遍历更新监听器证书 @@ -236,7 +236,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo return extCertSpec.Domain == d.config.Domain }) if len(extCertSpecs) == 0 { - return errors.New("extension certificate spec not found") + return errors.New("could not find any extension certificates") } // 批量修改扩展证书 diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go index 976816ed3..6c6facb26 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go @@ -193,19 +193,19 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } ignoredStatuses := []string{"offline"} - for _, domainInfo := range getDomainListResp.Result.Domains { - if lo.Contains(ignoredStatuses, domainInfo.Status) { + for _, domainItem := range getDomainListResp.Result.Domains { + if lo.Contains(ignoredStatuses, domainItem.Status) { continue } - domains = append(domains, domainInfo.Domain) + domains = append(domains, domainItem.Domain) } if len(getDomainListResp.Result.Domains) < getDomainListPageSize { break - } else { - getDomainListPageNumber++ } + + getDomainListPageNumber++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go index 6a8eebb4a..5bb078722 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go @@ -58,42 +58,10 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - // 查询域名列表 - // REF: https://docs.jdcloud.com/cn/video-on-demand/api/listdomains - var domainId int - listDomainsPageNumber := 1 - listDomainsPageSize := 100 - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - listDomainsReq := jdvod.NewListDomainsRequestWithoutParam() - listDomainsReq.SetPageNumber(listDomainsPageNumber) - listDomainsReq.SetPageSize(listDomainsPageSize) - listDomainsResp, err := d.sdkClient.ListDomains(listDomainsReq) - d.logger.Debug("sdk request 'vod.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'vod.ListDomains': %w", err) - } - - for _, domainInfo := range listDomainsResp.Result.Content { - if domainInfo.Name == d.config.Domain { - domainId, _ = strconv.Atoi(domainInfo.Id) - break - } - } - - if len(listDomainsResp.Result.Content) < listDomainsPageSize { - break - } else { - listDomainsPageNumber++ - } - } - if domainId == 0 { - return nil, errors.New("domain not found") + // 获取域名 ID + domainId, err := d.findDomainIdByDomain(ctx, d.config.Domain) + if err != nil { + return nil, err } // 查询域名 SSL 配置 @@ -125,6 +93,44 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) findDomainIdByDomain(ctx context.Context, domain string) (int, error) { + // 查询域名列表 + // REF: https://docs.jdcloud.com/cn/video-on-demand/api/listdomains + listDomainsPageNumber := 1 + listDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + + listDomainsReq := jdvod.NewListDomainsRequestWithoutParam() + listDomainsReq.SetPageNumber(listDomainsPageNumber) + listDomainsReq.SetPageSize(listDomainsPageSize) + listDomainsResp, err := d.sdkClient.ListDomains(listDomainsReq) + d.logger.Debug("sdk request 'vod.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return 0, fmt.Errorf("failed to execute sdk request 'vod.ListDomains': %w", err) + } + + for _, domainItem := range listDomainsResp.Result.Content { + if domainItem.Name == d.config.Domain { + domainId, _ := strconv.Atoi(domainItem.Id) + return domainId, nil + } + } + + if len(listDomainsResp.Result.Content) < listDomainsPageSize { + break + } + + listDomainsPageNumber++ + } + + return 0, fmt.Errorf("could not find domain '%s'", domain) +} + func createSDKClient(accessKeyId, accessKeySecret string) (*internal.VodClient, error) { clientCredentials := jdcore.NewCredentials(accessKeyId, accessKeySecret) client := internal.NewVodClient(clientCredentials) 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 1f43d2018..c7909b6b8 100644 --- a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go @@ -20,7 +20,7 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 金山云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` - // 域名匹配模式。 + // 域名匹配模式。暂时只支持精确匹配。 // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 @@ -83,69 +83,10 @@ func (d *SSLDeployerProvider) deployToDomain(ctx context.Context, certPEM string return errors.New("config `domain` is required") } - // 查询域名列表,获取域名 ID - // https://docs.ksyun.com/documents/198 - var domainId string - getCdnDomainsPageNumber := int32(1) - getCdnDomainsPageSize := int32(100) - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - getCdnDomainsInput := map[string]any{ - "PageNumber": getCdnDomainsPageNumber, - "PageSize": getCdnDomainsPageSize, - "DomainName": d.config.Domain, - "FuzzyMatch": "off", - } - getCdnDomainsReq, getCdnDomainsOutput := d.sdkClient.GetCdnDomainsPostRequest(&getCdnDomainsInput) - getCdnDomainsErr := getCdnDomainsReq.Send() - d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput)) - if getCdnDomainsErr != nil { - return fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", getCdnDomainsErr) - } - - type GetCdnDomainsResponse struct { - PageNumber int32 `json:"PageNumber"` - PageSize int32 `json:"PageSize"` - TotalCount int32 `json:"TotalCount"` - Domains []*struct { - DomainId string `json:"DomainId"` - DomainName string `json:"DomainName"` - Cname string `json:"Cname"` - CdnType string `json:"CdnType"` - CreatedTime string `json:"CreatedTime"` - ModifiedTime string `json:"ModifiedTime"` - Region string `json:"Region"` - } `json:"Domains"` - } - var getCdnDomainsResp *GetCdnDomainsResponse - mapstructure.Decode(getCdnDomainsOutput, &getCdnDomainsResp) - - if getCdnDomainsResp != nil { - for _, domainItem := range getCdnDomainsResp.Domains { - if strings.EqualFold(domainItem.DomainName, d.config.Domain) { - domainId = domainItem.DomainId - break - } - } - - if domainId != "" { - break - } - } - - if getCdnDomainsResp == nil || len(getCdnDomainsResp.Domains) < int(getCdnDomainsPageSize) { - break - } else { - getCdnDomainsPageNumber++ - } - } - if domainId == "" { - return errors.New("domain not found") + // 获取域名 ID + domainId, err := d.findDomainIdByDomain(ctx, d.config.Domain) + if err != nil { + return err } if err := d.updateDomainCertificate(ctx, domainId, certPEM, privkeyPEM); err != nil { @@ -178,6 +119,67 @@ func (d *SSLDeployerProvider) deployToCertificate(ctx context.Context, certPEM s return nil } +func (d *SSLDeployerProvider) findDomainIdByDomain(ctx context.Context, domain string) (string, error) { + // 查询域名列表 + // https://docs.ksyun.com/documents/198 + getCdnDomainsPageNumber := 1 + getCdnDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + getCdnDomainsInput := map[string]any{ + "PageNumber": getCdnDomainsPageNumber, + "PageSize": getCdnDomainsPageSize, + "DomainName": domain, + "FuzzyMatch": "off", + } + getCdnDomainsReq, getCdnDomainsOutput := d.sdkClient.GetCdnDomainsPostRequest(&getCdnDomainsInput) + getCdnDomainsErr := getCdnDomainsReq.Send() + d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput)) + if getCdnDomainsErr != nil { + return "", fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", getCdnDomainsErr) + } + + type GetCdnDomainsResponse struct { + PageNumber int32 `json:"PageNumber"` + PageSize int32 `json:"PageSize"` + TotalCount int32 `json:"TotalCount"` + Domains []*struct { + DomainId string `json:"DomainId"` + DomainName string `json:"DomainName"` + Cname string `json:"Cname"` + CdnType string `json:"CdnType"` + CreatedTime string `json:"CreatedTime"` + ModifiedTime string `json:"ModifiedTime"` + Region string `json:"Region"` + } `json:"Domains"` + } + var getCdnDomainsResp *GetCdnDomainsResponse + mapstructure.Decode(getCdnDomainsOutput, &getCdnDomainsResp) + if getCdnDomainsResp == nil { + break + } + + for _, domainItem := range getCdnDomainsResp.Domains { + if strings.EqualFold(domainItem.DomainName, domain) { + return domainItem.DomainId, nil + } + } + + if len(getCdnDomainsResp.Domains) < getCdnDomainsPageSize { + break + } + + getCdnDomainsPageNumber++ + } + + return "", fmt.Errorf("could not find domain '%s'", domain) +} + func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domainId string, certPEM string, privkeyPEM string) error { // 为加速域名配置证书接口 // https://docs.ksyun.com/documents/261 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 26974375f..f2aac07f8 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -164,8 +164,8 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w // 查询域名基本信息,获取匹配的域名 // REF: https://cloud.tencent.com/document/api/228/41118 - describeDomainsOffset := int64(0) - describeDomainsLimit := int64(100) + describeDomainsOffset := 0 + describeDomainsLimit := 100 for { select { case <-ctx.Done(): @@ -181,27 +181,29 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w Fuzzy: common.BoolPtr(true), }, } - describeDomainsReq.Offset = common.Int64Ptr(describeDomainsOffset) - describeDomainsReq.Limit = common.Int64Ptr(describeDomainsLimit) + describeDomainsReq.Offset = common.Int64Ptr(int64(describeDomainsOffset)) + describeDomainsReq.Limit = common.Int64Ptr(int64(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 _, domainInfo := range describeDomainsResp.Response.Domains { - if lo.FromPtr(domainInfo.Product) == "cdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domainInfo.Domain)) { - domains = append(domains, *domainInfo.Domain) - } + if describeDomainsResp.Response == nil { + break + } + + for _, domainItem := range describeDomainsResp.Response.Domains { + if lo.FromPtr(domainItem.Product) == "cdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domainItem.Domain)) { + domains = append(domains, lo.FromPtr(domainItem.Domain)) } } - if len(describeDomainsResp.Response.Domains) < int(describeDomainsLimit) { + if len(describeDomainsResp.Response.Domains) < describeDomainsLimit { break - } else { - describeDomainsOffset += describeDomainsLimit } + + describeDomainsOffset += describeDomainsLimit } return domains, nil @@ -246,7 +248,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai if err != nil { return fmt.Errorf("failed to execute sdk request 'cdn.DescribeDomainsConfig': %w", err) } else if len(describeDomainsConfigResp.Response.Domains) == 0 { - return fmt.Errorf("domain %s not found", domain) + return fmt.Errorf("could not find domain '%s'", domain) } domainConfig := describeDomainsConfigResp.Response.Domains[0] diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go index d56fba961..5fa884a4a 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -233,9 +233,10 @@ func (d *SSLDeployerProvider) deployToRuleDomain(ctx context.Context, cloudCertI return fmt.Errorf("failed to execute sdk request 'clb.DescribeTaskStatus': %w", err) } - if describeTaskStatusResp.Response.Status == nil || *describeTaskStatusResp.Response.Status == 1 { + status := lo.FromPtr(describeTaskStatusResp.Response.Status) + if status == 1 { return errors.New("unexpected tencentcloud task status") - } else if *describeTaskStatusResp.Response.Status == 0 { + } else if status == 0 { break } @@ -257,7 +258,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo if err != nil { return fmt.Errorf("failed to execute sdk request 'clb.DescribeListeners': %w", err) } else if len(describeListenersResp.Response.Listeners) == 0 { - return fmt.Errorf("listener %s not found", cloudListenerId) + return fmt.Errorf("could not find listener '%s'", cloudListenerId) } // 修改监听器属性 @@ -295,9 +296,10 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo return fmt.Errorf("failed to execute sdk request 'clb.DescribeTaskStatus': %w", err) } - if describeTaskStatusResp.Response.Status == nil || *describeTaskStatusResp.Response.Status == 1 { + status := lo.FromPtr(describeTaskStatusResp.Response.Status) + if status == 1 { return errors.New("unexpected tencentcloud task status") - } else if *describeTaskStatusResp.Response.Status == 0 { + } else if status == 0 { break } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-cos/tencentcloud_cos.go b/pkg/core/ssl-deployer/providers/tencentcloud-cos/tencentcloud_cos.go index dc35aaca0..49e5ca4cd 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cos/tencentcloud_cos.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cos/tencentcloud_cos.go @@ -7,6 +7,7 @@ import ( "log/slog" "time" + "github.com/samber/lo" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" tcssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" @@ -134,21 +135,11 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if describeHostDeployRecordDetailResp.Response.TotalCount == nil { return nil, errors.New("unexpected tencentcloud deployment job status") } else { - if describeHostDeployRecordDetailResp.Response.PendingTotalCount != nil { - pendingCount = *describeHostDeployRecordDetailResp.Response.PendingTotalCount - } - if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { - runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount - } - if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { - succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount - } - if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { - failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount - } - if describeHostDeployRecordDetailResp.Response.TotalCount != nil { - totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount - } + pendingCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.PendingTotalCount) + runningCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.RunningTotalCount) + succeededCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.SuccessTotalCount) + failedCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.FailedTotalCount) + totalCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.TotalCount) if succeededCount+failedCount == totalCount { if failedCount > 0 { @@ -168,8 +159,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke func (d *SSLDeployerProvider) checkIsBind(ctx context.Context, cloudCertId string) (bool, error) { // 查询证书 COS 云资源部署实例列表 // REF: https://cloud.tencent.com/document/api/400/91661 - describeHostCosInstanceListLimit := int64(100) - describeHostCosInstanceListOffset := int64(0) + describeHostCosInstanceListLimit := 100 + describeHostCosInstanceListOffset := 0 for { select { case <-ctx.Done(): @@ -181,33 +172,39 @@ func (d *SSLDeployerProvider) checkIsBind(ctx context.Context, cloudCertId strin describeHostCosInstanceListReq.OldCertificateId = common.StringPtr(cloudCertId) describeHostCosInstanceListReq.ResourceType = common.StringPtr("cos") describeHostCosInstanceListReq.IsCache = common.Uint64Ptr(0) - describeHostCosInstanceListReq.Offset = common.Int64Ptr(describeHostCosInstanceListOffset) - describeHostCosInstanceListReq.Limit = common.Int64Ptr(describeHostCosInstanceListLimit) + describeHostCosInstanceListReq.Offset = common.Int64Ptr(int64(describeHostCosInstanceListOffset)) + describeHostCosInstanceListReq.Limit = common.Int64Ptr(int64(describeHostCosInstanceListLimit)) describeHostCosInstanceListResp, err := d.sdkClient.SSL.DescribeHostCosInstanceList(describeHostCosInstanceListReq) d.logger.Debug("sdk request 'ssl.DescribeHostCosInstanceList'", slog.Any("request", describeHostCosInstanceListReq), slog.Any("response", describeHostCosInstanceListResp)) if err != nil { return false, fmt.Errorf("failed to execute sdk request 'ssl.DescribeHostCosInstanceList': %w", err) } + if describeHostCosInstanceListResp.Response == nil { + break + } + for _, instance := range describeHostCosInstanceListResp.Response.InstanceList { - if instance.Bucket == nil || *instance.Bucket != d.config.Bucket { + if lo.FromPtr(instance.Bucket) != d.config.Bucket { continue } - if instance.Domain == nil || *instance.Domain != d.config.Domain { + if lo.FromPtr(instance.Domain) != d.config.Domain { continue } - if instance.Status == nil || *instance.Status != "ENABLED" { + if lo.FromPtr(instance.Status) != "ENABLED" { continue } return true, nil } - if len(describeHostCosInstanceListResp.Response.InstanceList) < int(describeHostCosInstanceListLimit) { - return false, nil - } else { - describeHostCosInstanceListOffset += describeHostCosInstanceListLimit + if len(describeHostCosInstanceListResp.Response.InstanceList) < describeHostCosInstanceListLimit { + break } + + describeHostCosInstanceListOffset += describeHostCosInstanceListLimit } + + return false, nil } func createSDKClients(secretId, secretKey, region string) (*wSDKClients, error) { 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 0337c2669..ff3781a96 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -164,8 +164,8 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w // 查询域名基本信息,获取匹配的域名 // REF: https://cloud.tencent.com/document/api/228/41118 - describeDomainsOffset := int64(0) - describeDomainsLimit := int64(100) + describeDomainsOffset := 0 + describeDomainsLimit := 100 for { select { case <-ctx.Done(): @@ -181,27 +181,29 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w Fuzzy: common.BoolPtr(true), }, } - describeDomainsReq.Offset = common.Int64Ptr(describeDomainsOffset) - describeDomainsReq.Limit = common.Int64Ptr(describeDomainsLimit) + describeDomainsReq.Offset = common.Int64Ptr(int64(describeDomainsOffset)) + describeDomainsReq.Limit = common.Int64Ptr(int64(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 _, domainInfo := range describeDomainsResp.Response.Domains { - if lo.FromPtr(domainInfo.Product) == "ecdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domainInfo.Domain)) { - domains = append(domains, *domainInfo.Domain) - } + if describeDomainsResp.Response == nil { + break + } + + for _, domainItem := range describeDomainsResp.Response.Domains { + if lo.FromPtr(domainItem.Product) == "ecdn" && xcerthostname.IsMatch(wildcardDomain, lo.FromPtr(domainItem.Domain)) { + domains = append(domains, *domainItem.Domain) } } - if len(describeDomainsResp.Response.Domains) < int(describeDomainsLimit) { + if len(describeDomainsResp.Response.Domains) < describeDomainsLimit { break - } else { - describeDomainsOffset += describeDomainsLimit } + + describeDomainsOffset += describeDomainsLimit } return domains, nil @@ -246,7 +248,7 @@ func (d *SSLDeployerProvider) updateDomainHttpsServerCert(ctx context.Context, d if err != nil { return fmt.Errorf("failed to execute sdk request 'cdn.DescribeDomainsConfig': %w", err) } else if len(describeDomainsConfigResp.Response.Domains) == 0 { - return fmt.Errorf("domain %s not found", domain) + return fmt.Errorf("could not find domain '%s'", domain) } domainConfig := describeDomainsConfigResp.Response.Domains[0] diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go index ba4150e24..9aff904e5 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-eo/tencentcloud_eo.go @@ -195,11 +195,12 @@ func (d *SSLDeployerProvider) getAllDomainsInZone(ctx context.Context, zoneId st return nil, fmt.Errorf("failed to execute sdk request 'teo.DescribeAccelerationDomains': %w", err) } - for _, accelerationDomain := range describeAccelerationDomainsResp.Response.AccelerationDomains { - if accelerationDomain == nil || accelerationDomain.DomainName == nil { + for _, domainItem := range describeAccelerationDomainsResp.Response.AccelerationDomains { + if domainItem == nil || domainItem.DomainName == nil { continue } - domainsInZone = append(domainsInZone, *accelerationDomain.DomainName) + + domainsInZone = append(domainsInZone, *domainItem.DomainName) } if len(describeAccelerationDomainsResp.Response.AccelerationDomains) < pageSize { diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go b/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go index f3bb9ffab..61c41f752 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-gaap/tencentcloud_gaap.go @@ -130,7 +130,7 @@ func (d *SSLDeployerProvider) updateHttpsListenerCertificate(ctx context.Context if err != nil { return fmt.Errorf("failed to execute sdk request 'gaap.DescribeHTTPSListeners': %w", err) } else if len(describeHTTPSListenersResp.Response.ListenerSet) == 0 { - return fmt.Errorf("listener %s not found", cloudListenerId) + return fmt.Errorf("could not find listener '%s'", cloudListenerId) } // 修改 HTTPS 监听器配置 diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go index 7d1cb4de3..b7fd0abde 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go @@ -7,6 +7,7 @@ import ( "log/slog" "time" + "github.com/samber/lo" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" tcssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" @@ -130,21 +131,11 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if describeHostDeployRecordDetailResp.Response.TotalCount == nil { return nil, errors.New("unexpected tencentcloud deployment job status") } else { - if describeHostDeployRecordDetailResp.Response.PendingTotalCount != nil { - pendingCount = *describeHostDeployRecordDetailResp.Response.PendingTotalCount - } - if describeHostDeployRecordDetailResp.Response.RunningTotalCount != nil { - runningCount = *describeHostDeployRecordDetailResp.Response.RunningTotalCount - } - if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { - succeededCount = *describeHostDeployRecordDetailResp.Response.SuccessTotalCount - } - if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { - failedCount = *describeHostDeployRecordDetailResp.Response.FailedTotalCount - } - if describeHostDeployRecordDetailResp.Response.TotalCount != nil { - totalCount = *describeHostDeployRecordDetailResp.Response.TotalCount - } + pendingCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.PendingTotalCount) + runningCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.RunningTotalCount) + succeededCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.SuccessTotalCount) + failedCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.FailedTotalCount) + totalCount = lo.FromPtr(describeHostDeployRecordDetailResp.Response.TotalCount) if succeededCount+failedCount == totalCount { if failedCount > 0 { diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go index d16247166..925ca2a47 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ssl-update/tencentcloud_ssl_update.go @@ -8,6 +8,7 @@ import ( "slices" "time" + "github.com/samber/lo" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" tcssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" @@ -163,21 +164,11 @@ func (d *SSLDeployerProvider) executeUpdateCertificateInstance(ctx context.Conte if describeHostUpdateRecordDetailResp.Response.TotalCount == nil { return errors.New("unexpected tencentcloud deployment job status") } else { - if describeHostUpdateRecordDetailResp.Response.PendingTotalCount != nil { - pendingCount = *describeHostUpdateRecordDetailResp.Response.PendingTotalCount - } - if describeHostUpdateRecordDetailResp.Response.RunningTotalCount != nil { - runningCount = *describeHostUpdateRecordDetailResp.Response.RunningTotalCount - } - if describeHostUpdateRecordDetailResp.Response.SuccessTotalCount != nil { - succeededCount = *describeHostUpdateRecordDetailResp.Response.SuccessTotalCount - } - if describeHostUpdateRecordDetailResp.Response.FailedTotalCount != nil { - failedCount = *describeHostUpdateRecordDetailResp.Response.FailedTotalCount - } - if describeHostUpdateRecordDetailResp.Response.TotalCount != nil { - totalCount = *describeHostUpdateRecordDetailResp.Response.TotalCount - } + pendingCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.PendingTotalCount) + runningCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.RunningTotalCount) + succeededCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.SuccessTotalCount) + failedCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.FailedTotalCount) + totalCount = lo.FromPtr(describeHostUpdateRecordDetailResp.Response.TotalCount) if succeededCount+failedCount == totalCount { if failedCount > 0 { @@ -250,18 +241,10 @@ func (d *SSLDeployerProvider) executeUploadUpdateCertificateInstance(ctx context return errors.New("unexpected tencentcloud deployment job status") } else { for _, record := range describeHostUploadUpdateRecordDetailResp.Response.DeployRecordDetail { - if record.RunningTotalCount != nil { - runningCount = *record.RunningTotalCount - } - if record.SuccessTotalCount != nil { - succeededCount = *record.SuccessTotalCount - } - if record.FailedTotalCount != nil { - failedCount = *record.FailedTotalCount - } - if record.TotalCount != nil { - totalCount = *record.TotalCount - } + runningCount += lo.FromPtr(record.RunningTotalCount) + succeededCount += lo.FromPtr(record.SuccessTotalCount) + failedCount += lo.FromPtr(record.FailedTotalCount) + totalCount += lo.FromPtr(record.TotalCount) } if succeededCount+failedCount == totalCount { diff --git a/pkg/core/ssl-deployer/providers/ucloud-ucdn/ucloud_ucdn.go b/pkg/core/ssl-deployer/providers/ucloud-ucdn/ucloud_ucdn.go index 822680ae5..71be215c1 100644 --- a/pkg/core/ssl-deployer/providers/ucloud-ucdn/ucloud_ucdn.go +++ b/pkg/core/ssl-deployer/providers/ucloud-ucdn/ucloud_ucdn.go @@ -97,7 +97,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'ucdn.GetUcdnDomainConfig': %w", err) } else if len(getUcdnDomainConfigResp.DomainList) == 0 { - return nil, fmt.Errorf("domain %s not found", d.config.DomainId) + return nil, fmt.Errorf("could not find domain '%s'", d.config.DomainId) } // 更新 HTTPS 加速配置 diff --git a/pkg/core/ssl-deployer/providers/volcengine-alb/volcengine_alb.go b/pkg/core/ssl-deployer/providers/volcengine-alb/volcengine_alb.go index 9ab152188..2b9e2210b 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-alb/volcengine_alb.go +++ b/pkg/core/ssl-deployer/providers/volcengine-alb/volcengine_alb.go @@ -129,8 +129,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 HTTPS 监听器列表 // REF: https://www.volcengine.com/docs/6767/113684 listenerIds := make([]string, 0) - describeListenersPageSize := int64(100) - describeListenersPageNumber := int64(1) + describeListenersPageSize := 100 + describeListenersPageNumber := 1 for { select { case <-ctx.Done(): @@ -141,8 +141,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer describeListenersReq := &vealb.DescribeListenersInput{ LoadBalancerId: ve.String(d.config.LoadbalancerId), Protocol: ve.String("HTTPS"), - PageNumber: ve.Int64(describeListenersPageNumber), - PageSize: ve.Int64(describeListenersPageSize), + PageNumber: ve.Int64(int64(describeListenersPageNumber)), + PageSize: ve.Int64(int64(describeListenersPageSize)), } describeListenersResp, err := d.sdkClient.DescribeListeners(describeListenersReq) d.logger.Debug("sdk request 'alb.DescribeListeners'", slog.Any("request", describeListenersReq), slog.Any("response", describeListenersResp)) @@ -154,11 +154,11 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer listenerIds = append(listenerIds, *listener.ListenerId) } - if len(describeListenersResp.Listeners) < int(describeListenersPageSize) { + if len(describeListenersResp.Listeners) < describeListenersPageSize { break - } else { - describeListenersPageNumber++ } + + describeListenersPageNumber++ } // 遍历更新监听证书 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 9787e597f..d37617606 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -158,8 +158,8 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w // 查询加速域名列表,获取匹配的域名 // REF: https://www.volcengine.com/docs/6454/75269 - listCdnDomainsPageNum := int64(1) - listCdnDomainsPageSize := int64(100) + listCdnDomainsPageNum := 1 + listCdnDomainsPageSize := 100 for { select { case <-ctx.Done(): @@ -170,8 +170,8 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w listCdnDomainsReq := &vecdn.ListCdnDomainsInput{ Domain: ve.String(strings.TrimPrefix(wildcardDomain, "*.")), Status: ve.String("online"), - PageNum: ve.Int64(listCdnDomainsPageNum), - PageSize: ve.Int64(listCdnDomainsPageSize), + PageNum: ve.Int64(int64(listCdnDomainsPageNum)), + PageSize: ve.Int64(int64(listCdnDomainsPageSize)), } listCdnDomainsResp, err := d.sdkClient.ListCdnDomains(listCdnDomainsReq) d.logger.Debug("sdk request 'cdn.ListCdnDomains'", slog.Any("request", listCdnDomainsReq), slog.Any("response", listCdnDomainsResp)) @@ -179,19 +179,17 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCdnDomains': %w", err) } - if listCdnDomainsResp.Data != nil { - for _, domainInfo := range listCdnDomainsResp.Data { - if xcerthostname.IsMatch(wildcardDomain, ve.StringValue(domainInfo.Domain)) { - domains = append(domains, ve.StringValue(domainInfo.Domain)) - } + for _, domainItem := range listCdnDomainsResp.Data { + if xcerthostname.IsMatch(wildcardDomain, ve.StringValue(domainItem.Domain)) { + domains = append(domains, ve.StringValue(domainItem.Domain)) } } - if len(listCdnDomainsResp.Data) < int(listCdnDomainsPageSize) { + if len(listCdnDomainsResp.Data) < listCdnDomainsPageSize { break - } else { - listCdnDomainsPageSize++ } + + listCdnDomainsPageSize++ } if len(domains) == 0 { diff --git a/pkg/core/ssl-deployer/providers/volcengine-clb/volcengine_clb.go b/pkg/core/ssl-deployer/providers/volcengine-clb/volcengine_clb.go index 98f3d1223..2bfaaa9a0 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-clb/volcengine_clb.go +++ b/pkg/core/ssl-deployer/providers/volcengine-clb/volcengine_clb.go @@ -125,8 +125,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer // 查询 HTTPS 监听器列表 // REF: https://www.volcengine.com/docs/6406/71776 listenerIds := make([]string, 0) - describeListenersPageSize := int64(100) - describeListenersPageNumber := int64(1) + describeListenersPageSize := 100 + describeListenersPageNumber := 1 for { select { case <-ctx.Done(): @@ -137,8 +137,8 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer describeListenersReq := &veclb.DescribeListenersInput{ LoadBalancerId: ve.String(d.config.LoadbalancerId), Protocol: ve.String("HTTPS"), - PageNumber: ve.Int64(describeListenersPageNumber), - PageSize: ve.Int64(describeListenersPageSize), + PageNumber: ve.Int64(int64(describeListenersPageNumber)), + PageSize: ve.Int64(int64(describeListenersPageSize)), } describeListenersResp, err := d.sdkClient.DescribeListeners(describeListenersReq) d.logger.Debug("sdk request 'clb.DescribeListeners'", slog.Any("request", describeListenersReq), slog.Any("response", describeListenersResp)) @@ -150,11 +150,11 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer listenerIds = append(listenerIds, *listener.ListenerId) } - if len(describeListenersResp.Listeners) < int(describeListenersPageSize) { + if len(describeListenersResp.Listeners) < describeListenersPageSize { break - } else { - describeListenersPageNumber++ } + + describeListenersPageNumber++ } // 遍历更新监听证书 diff --git a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go index c202f6785..350655341 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-dcdn/volcengine_dcdn.go @@ -23,7 +23,7 @@ type SSLDeployerProviderConfig struct { AccessKeySecret string `json:"accessKeySecret"` // 火山引擎地域。 Region string `json:"region"` - // 域名匹配模式。 + // 域名匹配模式。暂时只支持精确匹配。 // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 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 6427eb435..53ceefa0d 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -172,8 +172,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro // 查询域名列表 // REF: https://www.volcengine.com/docs/6469/1126815 - listDomainDetailPageNum := int32(1) - listDomainDetailPageSize := int32(1000) + listDomainDetailPageNum := 1 + listDomainDetailPageSize := 1000 for { select { case <-ctx.Done(): @@ -183,8 +183,8 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro listDomainDetailReq := &velive.ListDomainDetailBody{ DomainStatusList: ve.Int32Slice([]int32{0}), - PageNum: listDomainDetailPageNum, - PageSize: listDomainDetailPageSize, + PageNum: int32(listDomainDetailPageNum), + PageSize: int32(listDomainDetailPageSize), } listDomainDetailResp, err := d.sdkClient.ListDomainDetail(ctx, listDomainDetailReq) d.logger.Debug("sdk request 'live.ListDomainDetail'", slog.Any("request", listDomainDetailReq), slog.Any("response", listDomainDetailResp)) @@ -192,17 +192,19 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return nil, fmt.Errorf("failed to execute sdk request 'live.ListDomainDetail': %w", err) } - if listDomainDetailResp.Result.DomainList != nil { - for _, domainInfo := range listDomainDetailResp.Result.DomainList { - domains = append(domains, domainInfo.Domain) - } + if listDomainDetailResp.Result == nil { + break } - if len(listDomainDetailResp.Result.DomainList) < int(listDomainDetailPageSize) { - break - } else { - listDomainDetailPageNum++ + for _, domainItem := range listDomainDetailResp.Result.DomainList { + domains = append(domains, domainItem.Domain) } + + if len(listDomainDetailResp.Result.DomainList) < listDomainDetailPageSize { + break + } + + listDomainDetailPageNum++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/wangsu-cdn/consts.go b/pkg/core/ssl-deployer/providers/wangsu-cdn/consts.go new file mode 100644 index 000000000..d4493904c --- /dev/null +++ b/pkg/core/ssl-deployer/providers/wangsu-cdn/consts.go @@ -0,0 +1,6 @@ +package wangsucdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go b/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go index 88c9a95ea..7f1b6ade5 100644 --- a/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go +++ b/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go @@ -20,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 网宿云 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名数组(支持泛域名)。 Domains []string `json:"domains"` } diff --git a/pkg/core/ssl-deployer/providers/wangsu-cdnpro/consts.go b/pkg/core/ssl-deployer/providers/wangsu-cdnpro/consts.go new file mode 100644 index 000000000..5cde7cfa8 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/wangsu-cdnpro/consts.go @@ -0,0 +1,6 @@ +package wangsucdnpro + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go b/pkg/core/ssl-deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go index 4c06d5a42..055dbaaab 100644 --- a/pkg/core/ssl-deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go +++ b/pkg/core/ssl-deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go @@ -32,6 +32,9 @@ type SSLDeployerProviderConfig struct { ApiKey string `json:"apiKey"` // 网宿云环境。 Environment string `json:"environment"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` // 证书 ID。 diff --git a/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go b/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go index 416ed65fb..1c5b82da0 100644 --- a/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go +++ b/pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go @@ -60,7 +60,7 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { // 避免重复上传 - if res, err := m.findCertIfExists(ctx, certPEM, privkeyPEM); err != nil { + if res, err := m.tryFindCert(ctx, certPEM, privkeyPEM); err != nil { return nil, err } else if res != nil { m.logger.Info("ssl certificate already exists") @@ -107,19 +107,19 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } // 获取刚刚上传证书 ID - if res, err := m.findCertIfExists(ctx, certPEM, privkeyPEM); err != nil { + if res, err := m.tryFindCert(ctx, certPEM, privkeyPEM); err != nil { return nil, err } else if res == nil { - return nil, fmt.Errorf("no ssl certificate found, may be upload failed") + return nil, fmt.Errorf("could not find ssl certificate, may be upload failed") } else { return res, nil } } -func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { - searchWebsiteSSLPageNumber := int32(1) - searchWebsiteSSLPageSize := int32(100) - searchWebsiteSSLItemsCount := int32(0) +func (m *SSLManagerProvider) tryFindCert(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { + searchWebsiteSSLPageNumber := 1 + searchWebsiteSSLPageSize := 100 + searchWebsiteSSLItemsCount := 0 for { select { case <-ctx.Done(): @@ -131,8 +131,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin case *onepanelsdk.Client: { searchWebsiteSSLReq := &onepanelsdk.SearchWebsiteSSLRequest{ - Page: searchWebsiteSSLPageNumber, - PageSize: searchWebsiteSSLPageSize, + Page: int32(searchWebsiteSSLPageNumber), + PageSize: int32(searchWebsiteSSLPageSize), } searchWebsiteSSLResp, err := sdkClient.SearchWebsiteSSL(searchWebsiteSSLReq) m.logger.Debug("sdk request '1panel.SearchWebsiteSSL'", slog.Any("request", searchWebsiteSSLReq), slog.Any("response", searchWebsiteSSLResp)) @@ -140,27 +140,29 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request '1panel.SearchWebsiteSSL': %w", err) } - if searchWebsiteSSLResp.Data != nil { - for _, sslItem := range searchWebsiteSSLResp.Data.Items { - if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) && - strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) { - // 如果已存在相同证书,直接返回 - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", sslItem.ID), - CertName: sslItem.Description, - }, nil - } + if searchWebsiteSSLResp.Data == nil { + break + } + + for _, sslItem := range searchWebsiteSSLResp.Data.Items { + if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) && + strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) { + // 如果已存在相同证书,直接返回 + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", sslItem.ID), + CertName: sslItem.Description, + }, nil } } - searchWebsiteSSLItemsCount = searchWebsiteSSLResp.Data.Total + searchWebsiteSSLItemsCount = int(searchWebsiteSSLResp.Data.Total) } case *onepanelsdkv2.Client: { searchWebsiteSSLReq := &onepanelsdkv2.SearchWebsiteSSLRequest{ - Page: searchWebsiteSSLPageNumber, - PageSize: searchWebsiteSSLPageSize, + Page: int32(searchWebsiteSSLPageNumber), + PageSize: int32(searchWebsiteSSLPageSize), } searchWebsiteSSLResp, err := sdkClient.SearchWebsiteSSL(searchWebsiteSSLReq) m.logger.Debug("sdk request '1panel.SearchWebsiteSSL'", slog.Any("request", searchWebsiteSSLReq), slog.Any("response", searchWebsiteSSLResp)) @@ -168,20 +170,22 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request '1panel.SearchWebsiteSSL': %w", err) } - if searchWebsiteSSLResp.Data != nil { - for _, sslItem := range searchWebsiteSSLResp.Data.Items { - if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) && - strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) { - // 如果已存在相同证书,直接返回 - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", sslItem.ID), - CertName: sslItem.Description, - }, nil - } + if searchWebsiteSSLResp.Data == nil { + break + } + + for _, sslItem := range searchWebsiteSSLResp.Data.Items { + if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) && + strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) { + // 如果已存在相同证书,直接返回 + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", sslItem.ID), + CertName: sslItem.Description, + }, nil } } - searchWebsiteSSLItemsCount = searchWebsiteSSLResp.Data.Total + searchWebsiteSSLItemsCount = int(searchWebsiteSSLResp.Data.Total) } default: diff --git a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go index 7b87cdba6..f139920d7 100644 --- a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go +++ b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go @@ -73,8 +73,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listusercertificateorder // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-getusercertificatedetail - listUserCertificateOrderPage := int64(1) - listUserCertificateOrderLimit := int64(50) + listUserCertificateOrderPage := 1 + listUserCertificateOrderLimit := 50 for { select { case <-ctx.Done(): @@ -84,8 +84,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listUserCertificateOrderReq := &alicas.ListUserCertificateOrderRequest{ ResourceGroupId: lo.EmptyableToPtr(m.config.ResourceGroupId), - CurrentPage: tea.Int64(listUserCertificateOrderPage), - ShowSize: tea.Int64(listUserCertificateOrderLimit), + CurrentPage: tea.Int64(int64(listUserCertificateOrderPage)), + ShowSize: tea.Int64(int64(listUserCertificateOrderLimit)), OrderType: tea.String("CERT"), } listUserCertificateOrderResp, err := m.sdkClient.ListUserCertificateOrderWithContext(context.TODO(), listUserCertificateOrderReq, &dara.RuntimeOptions{}) @@ -94,53 +94,55 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'cas.ListUserCertificateOrder': %w", err) } - if listUserCertificateOrderResp.Body.CertificateOrderList != nil { - for _, certOrder := range listUserCertificateOrderResp.Body.CertificateOrderList { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, tea.StringValue(certOrder.CommonName)) { - continue - } - - // 再对比证书序列号 - // 注意阿里云 CAS 会在序列号前补零,需去除后再比较 - oldCertSN := strings.TrimLeft(tea.StringValue(certOrder.SerialNo), "0") - newCertSN := strings.TrimLeft(certX509.SerialNumber.Text(16), "0") - if !strings.EqualFold(newCertSN, oldCertSN) { - continue - } - - // 最后对比证书内容 - getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{ - CertId: certOrder.CertificateId, - } - getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(context.TODO(), getUserCertificateDetailReq, &dara.RuntimeOptions{}) - m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err) - } else { - if !xcert.EqualCertificatesFromPEM(certPEM, tea.StringValue(getUserCertificateDetailResp.Body.Cert)) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", tea.Int64Value(certOrder.CertificateId)), - CertName: *certOrder.Name, - ExtendedData: map[string]any{ - "InstanceId": tea.StringValue(getUserCertificateDetailResp.Body.InstanceId), - "CertIdentifier": tea.StringValue(getUserCertificateDetailResp.Body.CertIdentifier), - }, - }, nil - } - } - - if listUserCertificateOrderResp.Body.CertificateOrderList == nil || len(listUserCertificateOrderResp.Body.CertificateOrderList) < int(listUserCertificateOrderLimit) { + if listUserCertificateOrderResp.Body == nil { break - } else { - listUserCertificateOrderPage++ } + + for _, certItem := range listUserCertificateOrderResp.Body.CertificateOrderList { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, tea.StringValue(certItem.CommonName)) { + continue + } + + // 对比证书序列号 + // 注意阿里云 CAS 会在序列号前补零,需去除后再比较 + oldCertSN := strings.TrimLeft(tea.StringValue(certItem.SerialNo), "0") + newCertSN := strings.TrimLeft(certX509.SerialNumber.Text(16), "0") + if !strings.EqualFold(newCertSN, oldCertSN) { + continue + } + + // 对比证书内容 + getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{ + CertId: certItem.CertificateId, + } + getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(context.TODO(), getUserCertificateDetailReq, &dara.RuntimeOptions{}) + m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err) + } else { + if !xcert.EqualCertificatesFromPEM(certPEM, tea.StringValue(getUserCertificateDetailResp.Body.Cert)) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", tea.Int64Value(certItem.CertificateId)), + CertName: *certItem.Name, + ExtendedData: map[string]any{ + "InstanceId": tea.StringValue(getUserCertificateDetailResp.Body.InstanceId), + "CertIdentifier": tea.StringValue(getUserCertificateDetailResp.Body.CertIdentifier), + }, + }, nil + } + + if len(listUserCertificateOrderResp.Body.CertificateOrderList) < listUserCertificateOrderLimit { + break + } + + listUserCertificateOrderPage++ } // 生成新证书名(需符合阿里云命名规则) diff --git a/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go b/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go index 398a9fb9d..e9e18dd7e 100644 --- a/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go +++ b/pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go @@ -87,22 +87,22 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if describeServerCertificatesResp.Body.ServerCertificates != nil && describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate != nil { fingerprint := sha256.Sum256(certX509.Raw) fingerprintHex := hex.EncodeToString(fingerprint[:]) - for _, serverCert := range describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate { - if tea.Int32Value(serverCert.IsAliCloudCertificate) != 0 { + for _, certItem := range describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate { + if tea.Int32Value(certItem.IsAliCloudCertificate) != 0 { continue } - if !strings.EqualFold(certX509.Subject.CommonName, tea.StringValue(serverCert.CommonName)) { + if !strings.EqualFold(certX509.Subject.CommonName, tea.StringValue(certItem.CommonName)) { continue } - if !strings.EqualFold(fingerprintHex, strings.ReplaceAll(tea.StringValue(serverCert.Fingerprint), ":", "")) { + if !strings.EqualFold(fingerprintHex, strings.ReplaceAll(tea.StringValue(certItem.Fingerprint), ":", "")) { continue } // 如果已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: *serverCert.ServerCertificateId, - CertName: *serverCert.ServerCertificateName, + CertId: *certItem.ServerCertificateId, + CertName: *certItem.ServerCertificateName, }, nil } } diff --git a/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go b/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go index 4ed3482dc..e0bfe8b6a 100644 --- a/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go +++ b/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go @@ -73,8 +73,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 获取证书列表,避免重复上传 // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListCertificates.html - var listCertificatesNextToken *string = nil - var listCertificatesMaxItems int32 = 1000 + // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_GetCertificate.html + listCertificatesNextToken := (*string)(nil) for { select { case <-ctx.Done(): @@ -84,7 +84,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listCertificatesReq := &awsacm.ListCertificatesInput{ NextToken: listCertificatesNextToken, - MaxItems: aws.Int32(listCertificatesMaxItems), + MaxItems: aws.Int32(1000), } listCertificatesResp, err := m.sdkClient.ListCertificates(context.TODO(), listCertificatesReq) m.logger.Debug("sdk request 'acm.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp)) @@ -92,24 +92,23 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'acm.ListCertificates': %w", err) } - for _, certSummary := range listCertificatesResp.CertificateSummaryList { - // 先对比证书有效期 - if certSummary.NotBefore == nil || !certSummary.NotBefore.Equal(certX509.NotBefore) { + for _, certItem := range listCertificatesResp.CertificateSummaryList { + // 对比证书有效期 + if certItem.NotBefore == nil || !certItem.NotBefore.Equal(certX509.NotBefore) { continue } - if certSummary.NotAfter == nil || !certSummary.NotAfter.Equal(certX509.NotAfter) { + if certItem.NotAfter == nil || !certItem.NotAfter.Equal(certX509.NotAfter) { continue } - // 再对比证书多域名 - if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), strings.Join(certSummary.SubjectAlternativeNameSummaries, ",")) { + // 对比证书多域名 + if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), strings.Join(certItem.SubjectAlternativeNameSummaries, ",")) { continue } - // 最后对比证书内容 - // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_GetCertificate.html + // 对比证书内容 getCertificateReq := &awsacm.GetCertificateInput{ - CertificateArn: certSummary.CertificateArn, + CertificateArn: certItem.CertificateArn, } getCertificateResp, err := m.sdkClient.GetCertificate(context.TODO(), getCertificateReq) if err != nil { @@ -123,15 +122,15 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: *certSummary.CertificateArn, + CertId: *certItem.CertificateArn, }, nil } - if listCertificatesResp.NextToken == nil || len(listCertificatesResp.CertificateSummaryList) < int(listCertificatesMaxItems) { + if len(listCertificatesResp.CertificateSummaryList) == 0 || listCertificatesResp.NextToken == nil { break - } else { - listCertificatesNextToken = listCertificatesResp.NextToken } + + listCertificatesNextToken = listCertificatesResp.NextToken } // 导入证书 diff --git a/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go b/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go index ab2c7be89..65e687836 100644 --- a/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go +++ b/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go @@ -76,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 获取证书列表,避免重复上传 // REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_ListServerCertificates.html - var listServerCertificatesMarker *string = nil - var listServerCertificatesMaxItems int32 = 1000 + // REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_GetServerCertificate.html + listServerCertificatesMarker := (*string)(nil) for { select { case <-ctx.Done(): @@ -87,7 +87,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listServerCertificatesReq := &awsiam.ListServerCertificatesInput{ Marker: listServerCertificatesMarker, - MaxItems: aws.Int32(listServerCertificatesMaxItems), + MaxItems: aws.Int32(1000), } if m.config.CertificatePath != "" { listServerCertificatesReq.PathPrefix = aws.String(m.config.CertificatePath) @@ -98,21 +98,20 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'iam.ListServerCertificates': %w", err) } - for _, certMeta := range listServerCertificatesResp.ServerCertificateMetadataList { - // 先对比证书路径 - if m.config.CertificatePath != "" && aws.ToString(certMeta.Path) != m.config.CertificatePath { + for _, certItem := range listServerCertificatesResp.ServerCertificateMetadataList { + // 对比证书路径 + if m.config.CertificatePath != "" && aws.ToString(certItem.Path) != m.config.CertificatePath { continue } - // 先对比证书有效期 - if certMeta.Expiration == nil || !certMeta.Expiration.Equal(certX509.NotAfter) { + // 对比证书有效期 + if certItem.Expiration == nil || !certItem.Expiration.Equal(certX509.NotAfter) { continue } - // 最后对比证书内容 - // REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_GetServerCertificate.html + // 对比证书内容 getServerCertificateReq := &awsiam.GetServerCertificateInput{ - ServerCertificateName: certMeta.ServerCertificateName, + ServerCertificateName: certItem.ServerCertificateName, } getServerCertificateResp, err := m.sdkClient.GetServerCertificate(context.TODO(), getServerCertificateReq) if err != nil { @@ -126,16 +125,16 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: aws.ToString(certMeta.ServerCertificateId), - CertName: aws.ToString(certMeta.ServerCertificateName), + CertId: aws.ToString(certItem.ServerCertificateId), + CertName: aws.ToString(certItem.ServerCertificateName), }, nil } - if listServerCertificatesResp.Marker == nil || len(listServerCertificatesResp.ServerCertificateMetadataList) < int(listServerCertificatesMaxItems) { + if len(listServerCertificatesResp.ServerCertificateMetadataList) == 0 || listServerCertificatesResp.Marker == nil { break - } else { - listServerCertificatesMarker = listServerCertificatesResp.Marker } + + listServerCertificatesMarker = listServerCertificatesResp.Marker } // 生成新证书名(需符合 AWS IAM 命名规则) diff --git a/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go b/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go index beda65415..e5aabcc76 100644 --- a/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go +++ b/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go @@ -86,35 +86,35 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'keyvault.GetCertificates': %w", err) } - for _, certProp := range page.Value { - // 先对比证书有效期 - if certProp.Attributes == nil { + for _, certItem := range page.Value { + // 对比证书有效期 + if certItem.Attributes == nil { continue } - if certProp.Attributes.NotBefore == nil || !certProp.Attributes.NotBefore.Equal(certX509.NotBefore) { + if certItem.Attributes.NotBefore == nil || !certItem.Attributes.NotBefore.Equal(certX509.NotBefore) { continue } - if certProp.Attributes.Expires == nil || !certProp.Attributes.Expires.Equal(certX509.NotAfter) { + if certItem.Attributes.Expires == nil || !certItem.Attributes.Expires.Equal(certX509.NotAfter) { continue } - // 再对比 Tag 中的通用名称 - if v, ok := certProp.Tags[TAG_CERTCN]; !ok || v == nil { + // 对比 Tag 中的通用名称 + if v, ok := certItem.Tags[TAG_CERTCN]; !ok || v == nil { continue } else if *v != certCN { continue } - // 再对比 Tag 中的序列号 - if v, ok := certProp.Tags[TAG_CERTSN]; !ok || v == nil { + // 对比 Tag 中的序列号 + if v, ok := certItem.Tags[TAG_CERTSN]; !ok || v == nil { continue } else if *v != certSN { continue } - // 最后对比证书内容 - getCertificateResp, err := m.sdkClient.GetCertificate(context.TODO(), certProp.ID.Name(), certProp.ID.Version(), nil) - m.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", certProp.ID.Name()), slog.String("request.certificateVersion", certProp.ID.Version()), slog.Any("response", getCertificateResp)) + // 对比证书内容 + getCertificateResp, err := m.sdkClient.GetCertificate(context.TODO(), certItem.ID.Name(), certItem.ID.Version(), nil) + m.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", certItem.ID.Name()), slog.String("request.certificateVersion", certItem.ID.Version()), slog.Any("response", getCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'keyvault.GetCertificate': %w", err) } else { @@ -126,8 +126,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: string(*certProp.ID), - CertName: certProp.ID.Name(), + CertId: string(*certItem.ID), + CertName: certItem.ID.Name(), }, nil } } diff --git a/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go b/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go index e27e5d852..240822f84 100644 --- a/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go +++ b/pkg/core/ssl-manager/providers/baiducloud-cert/baiducloud_cert.go @@ -67,27 +67,27 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cert.ListCertDetail': %w", err) } else { - for _, certDetail := range listCertDetail.Certs { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certDetail.CertCommonName) { + for _, certItem := range listCertDetail.Certs { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CertCommonName) { continue } - // 再对比证书有效期 - oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certDetail.CertStartTime) - oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certDetail.CertStopTime) + // 对比证书有效期 + oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certItem.CertStartTime) + oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certItem.CertStopTime) if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { continue } - // 再对比证书多域名 - if certDetail.CertDNSNames != strings.Join(certX509.DNSNames, ",") { + // 对比证书多域名 + if certItem.CertDNSNames != strings.Join(certX509.DNSNames, ",") { continue } - // 最后对比证书内容 - getCertDetailResp, err := m.sdkClient.GetCertRawData(certDetail.CertId) - m.logger.Debug("sdk request 'cert.GetCertRawData'", slog.Any("certId", certDetail.CertId), slog.Any("response", getCertDetailResp)) + // 对比证书内容 + getCertDetailResp, err := m.sdkClient.GetCertRawData(certItem.CertId) + m.logger.Debug("sdk request 'cert.GetCertRawData'", slog.Any("certId", certItem.CertId), slog.Any("response", getCertDetailResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cert.GetCertRawData': %w", err) } else { @@ -99,8 +99,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certDetail.CertId, - CertName: certDetail.CertName, + CertId: certItem.CertId, + CertName: certItem.CertName, }, nil } } diff --git a/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go b/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go index 32a4b6ed8..bd089f486 100644 --- a/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go +++ b/pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go @@ -65,14 +65,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-listcertinfo - listCertInfoPageNum := int64(1) - listCertInfoPageSize := int64(100) - listCertInfoTotal := 0 - listCertInfoReq := &bytepluscdn.ListCertInfoRequest{ - PageNum: bytepluscdn.GetInt64Ptr(listCertInfoPageNum), - PageSize: bytepluscdn.GetInt64Ptr(listCertInfoPageSize), - Source: bytepluscdn.GetStrPtr("cert_center"), - } + listCertInfoPageNum := 1 + listCertInfoPageSize := 100 for { select { case <-ctx.Done(): @@ -80,36 +74,43 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey default: } + listCertInfoReq := &bytepluscdn.ListCertInfoRequest{ + PageNum: bytepluscdn.GetInt64Ptr(int64(listCertInfoPageNum)), + PageSize: bytepluscdn.GetInt64Ptr(int64(listCertInfoPageSize)), + Source: bytepluscdn.GetStrPtr("cert_center"), + } listCertInfoResp, err := m.sdkClient.ListCertInfo(listCertInfoReq) m.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCertInfo': %w", err) } - if listCertInfoResp.Result.CertInfo != nil { - for _, certInfo := range listCertInfoResp.Result.CertInfo { - fingerprintSha1 := sha1.Sum(certX509.Raw) - fingerprintSha256 := sha256.Sum256(certX509.Raw) - isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certInfo.CertFingerprint.Sha1) && - strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certInfo.CertFingerprint.Sha256) - // 如果已存在相同证书,直接返回 - if isSameCert { - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: certInfo.CertId, - CertName: certInfo.Desc, - }, nil - } + for _, certItem := range listCertInfoResp.Result.CertInfo { + // 对比证书 SHA-1 摘要 + fingerprintSha1 := sha1.Sum(certX509.Raw) + if !strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certItem.CertFingerprint.Sha1) { + continue } + + // 对比证书 SHA-256 摘要 + fingerprintSha256 := sha256.Sum256(certX509.Raw) + if !strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certItem.CertFingerprint.Sha256) { + continue + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.CertId, + CertName: certItem.Desc, + }, nil } - listCertInfoLen := len(listCertInfoResp.Result.CertInfo) - if listCertInfoLen < int(listCertInfoPageSize) || int(listCertInfoResp.Result.Total) <= listCertInfoTotal+listCertInfoLen { + if len(listCertInfoResp.Result.CertInfo) < listCertInfoPageSize { break - } else { - listCertInfoPageNum++ - listCertInfoTotal += listCertInfoLen } + + listCertInfoPageNum++ } // 生成新证书名(需符合 BytePlus 命名规则) diff --git a/pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go b/pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go index 3814a3723..2d96b2722 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go @@ -65,8 +65,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询用户名下证书列表,避免重复上传 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13175&data=174&isNormal=1&vid=167 - listCertPage := int32(1) - listCertPerPage := int32(1000) + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13015&data=174&isNormal=1&vid=167 + listCertPage := 1 + listCertPerPage := 1000 for { select { case <-ctx.Done(): @@ -75,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } listCertsReq := &ctyunao.ListCertsRequest{ - Page: lo.ToPtr(listCertPage), - PerPage: lo.ToPtr(listCertPerPage), + Page: lo.ToPtr(int32(listCertPage)), + PerPage: lo.ToPtr(int32(listCertPerPage)), UsageMode: lo.ToPtr(int32(0)), } listCertsResp, err := m.sdkClient.ListCerts(listCertsReq) @@ -85,55 +86,55 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'ao.ListCerts': %w", err) } - if listCertsResp.ReturnObj != nil { - for _, certRecord := range listCertsResp.ReturnObj.Results { - // 对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) { - continue - } - - // 对比证书扩展名称 - if !slices.Equal(certX509.DNSNames, certRecord.SANs) { - continue - } - - // 对比证书有效期 - if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) { - continue - } else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) { - continue - } - - // 最后对比证书内容 - // 查询证书详情 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13015&data=174&isNormal=1&vid=167 - queryCertReq := &ctyunao.QueryCertRequest{ - Id: lo.ToPtr(certRecord.Id), - } - queryCertResp, err := m.sdkClient.QueryCert(queryCertReq) - m.logger.Debug("sdk request 'ao.QueryCert'", slog.Any("request", queryCertReq), slog.Any("response", queryCertResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'ao.QueryCert': %w", err) - } else if queryCertResp.ReturnObj != nil && queryCertResp.ReturnObj.Result != nil { - if !xcert.EqualCertificatesFromPEM(certPEM, queryCertResp.ReturnObj.Result.Certs) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", queryCertResp.ReturnObj.Result.Id), - CertName: queryCertResp.ReturnObj.Result.Name, - }, nil - } - } - - if listCertsResp.ReturnObj == nil || len(listCertsResp.ReturnObj.Results) < int(listCertPerPage) { + if listCertsResp.ReturnObj == nil { break - } else { - listCertPage++ } + + for _, certItem := range listCertsResp.ReturnObj.Results { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CN) { + continue + } + + // 对比证书扩展名称 + if !slices.Equal(certX509.DNSNames, certItem.SANs) { + continue + } + + // 对比证书有效期 + if !certX509.NotBefore.Equal(time.Unix(certItem.IssueTime, 0).UTC()) { + continue + } else if !certX509.NotAfter.Equal(time.Unix(certItem.ExpiresTime, 0).UTC()) { + continue + } + + // 对比证书内容 + queryCertReq := &ctyunao.QueryCertRequest{ + Id: lo.ToPtr(certItem.Id), + } + queryCertResp, err := m.sdkClient.QueryCert(queryCertReq) + m.logger.Debug("sdk request 'ao.QueryCert'", slog.Any("request", queryCertReq), slog.Any("response", queryCertResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ao.QueryCert': %w", err) + } else if queryCertResp.ReturnObj != nil && queryCertResp.ReturnObj.Result != nil { + if !xcert.EqualCertificatesFromPEM(certPEM, queryCertResp.ReturnObj.Result.Certs) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", queryCertResp.ReturnObj.Result.Id), + CertName: queryCertResp.ReturnObj.Result.Name, + }, nil + } + + if len(listCertsResp.ReturnObj.Results) < listCertPerPage { + break + } + + listCertPage++ } // 生成新证书名(需符合天翼云命名规则) diff --git a/pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go b/pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go index b5647cc3f..8feb53996 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go @@ -65,8 +65,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=10901&data=161&isNormal=1&vid=154 - queryCertListPage := int32(1) - queryCertListPerPage := int32(1000) + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=10899&data=161&isNormal=1&vid=154 + queryCertListPage := 1 + queryCertListPerPage := 1000 for { select { case <-ctx.Done(): @@ -75,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } queryCertListReq := &ctyuncdn.QueryCertListRequest{ - Page: lo.ToPtr(queryCertListPage), - PerPage: lo.ToPtr(queryCertListPerPage), + Page: lo.ToPtr(int32(queryCertListPage)), + PerPage: lo.ToPtr(int32(queryCertListPerPage)), UsageMode: lo.ToPtr(int32(0)), } queryCertListResp, err := m.sdkClient.QueryCertList(queryCertListReq) @@ -85,55 +86,55 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryCertList': %w", err) } - if queryCertListResp.ReturnObj != nil { - for _, certRecord := range queryCertListResp.ReturnObj.Results { - // 对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) { - continue - } - - // 对比证书扩展名称 - if !slices.Equal(certX509.DNSNames, certRecord.SANs) { - continue - } - - // 对比证书有效期 - if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) { - continue - } else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) { - continue - } - - // 最后对比证书内容 - // 查询证书详情 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=10899&data=161&isNormal=1&vid=154 - queryCertDetailReq := &ctyuncdn.QueryCertDetailRequest{ - Id: lo.ToPtr(certRecord.Id), - } - queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) - m.logger.Debug("sdk request 'cdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryCertDetail': %w", err) - } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { - if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), - CertName: queryCertDetailResp.ReturnObj.Result.Name, - }, nil - } - } - - if queryCertListResp.ReturnObj == nil || len(queryCertListResp.ReturnObj.Results) < int(queryCertListPerPage) { + if queryCertListResp.ReturnObj == nil { break - } else { - queryCertListPage++ } + + for _, certItem := range queryCertListResp.ReturnObj.Results { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CN) { + continue + } + + // 对比证书扩展名称 + if !slices.Equal(certX509.DNSNames, certItem.SANs) { + continue + } + + // 对比证书有效期 + if !certX509.NotBefore.Equal(time.Unix(certItem.IssueTime, 0).UTC()) { + continue + } else if !certX509.NotAfter.Equal(time.Unix(certItem.ExpiresTime, 0).UTC()) { + continue + } + + // 对比证书内容 + queryCertDetailReq := &ctyuncdn.QueryCertDetailRequest{ + Id: lo.ToPtr(certItem.Id), + } + queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) + m.logger.Debug("sdk request 'cdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryCertDetail': %w", err) + } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { + if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), + CertName: queryCertDetailResp.ReturnObj.Result.Name, + }, nil + } + + if len(queryCertListResp.ReturnObj.Results) < queryCertListPerPage { + break + } + + queryCertListPage++ } // 生成新证书名(需符合天翼云命名规则) diff --git a/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go b/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go index e179566c8..8b1ffb16b 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go @@ -59,7 +59,10 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { // 避免重复上传 - if res, _ := m.findCertIfExists(ctx, certPEM); res != nil { + if res, err := m.tryFindCert(ctx, certPEM); err != nil { + return nil, err + } else if res != nil { + m.logger.Info("ssl certificate already exists") return res, nil } @@ -85,7 +88,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey m.logger.Debug("sdk request 'cms.UploadCertificate'", slog.Any("request", uploadCertificateReq), slog.Any("response", uploadCertificateResp)) if err != nil { if uploadCertificateResp != nil && uploadCertificateResp.GetError() == "CCMS_100000067" { - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res == nil { return nil, errors.New("ctyun cms: no certificate found") @@ -99,16 +102,16 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } // 获取刚刚上传证书 ID - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res == nil { - return nil, fmt.Errorf("no ssl certificate found, may be upload failed") + return nil, fmt.Errorf("could not find ssl certificate, may be upload failed") } else { return res, nil } } -func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { +func (m *SSLManagerProvider) tryFindCert(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { // 解析证书内容 certX509, err := xcert.ParseCertificateFromPEM(certPEM) if err != nil { @@ -117,8 +120,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin // 查询用户证书列表 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=152&api=17233&data=204&isNormal=1&vid=283 - getCertificateListPageNum := int32(1) - getCertificateListPageSize := int32(10) + getCertificateListPageNum := 1 + getCertificateListPageSize := 10 for { select { case <-ctx.Done(): @@ -127,8 +130,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin } getCertificateListReq := &ctyuncms.GetCertificateListRequest{ - PageNum: lo.ToPtr(getCertificateListPageNum), - PageSize: lo.ToPtr(getCertificateListPageSize), + PageNum: lo.ToPtr(int32(getCertificateListPageNum)), + PageSize: lo.ToPtr(int32(getCertificateListPageSize)), Keyword: lo.ToPtr(certX509.Subject.CommonName), Origin: lo.ToPtr("UPLOAD"), } @@ -138,44 +141,45 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request 'cms.GetCertificateList': %w", err) } - if getCertificateListResp.ReturnObj != nil { + if getCertificateListResp.ReturnObj == nil { + break + } + + for _, certItem := range getCertificateListResp.ReturnObj.List { + // 对比证书名称 + if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), certItem.DomainName) { + continue + } + + // 对比证书有效期 + oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certItem.IssueTime) + oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certItem.ExpireTime) + if !certX509.NotBefore.Equal(oldCertNotBefore) { + continue + } else if !certX509.NotAfter.Equal(oldCertNotAfter) { + continue + } + + // 对比证书指纹 fingerprint := sha1.Sum(certX509.Raw) fingerprintHex := hex.EncodeToString(fingerprint[:]) - - for _, certRecord := range getCertificateListResp.ReturnObj.List { - // 对比证书名称 - if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), certRecord.DomainName) { - continue - } - - // 对比证书有效期 - oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certRecord.IssueTime) - oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certRecord.ExpireTime) - if !certX509.NotBefore.Equal(oldCertNotBefore) { - continue - } else if !certX509.NotAfter.Equal(oldCertNotAfter) { - continue - } - - // 对比证书指纹 - if !strings.EqualFold(fingerprintHex, certRecord.Fingerprint) { - continue - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: string(*&certRecord.Id), - CertName: certRecord.Name, - }, nil + if !strings.EqualFold(fingerprintHex, certItem.Fingerprint) { + continue } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.Id, + CertName: certItem.Name, + }, nil } - if getCertificateListResp.ReturnObj == nil || len(getCertificateListResp.ReturnObj.List) < int(getCertificateListPageSize) { + if len(getCertificateListResp.ReturnObj.List) < getCertificateListPageSize { break - } else { - getCertificateListPageNum++ } + + getCertificateListPageNum++ } return nil, nil diff --git a/pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go b/pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go index 2d974ea53..d5808632f 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go @@ -68,13 +68,13 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err) } else { - for _, certRecord := range listCertificatesResp.ReturnObj { + for _, certItem := range listCertificatesResp.ReturnObj { // 如果已存在相同证书,直接返回 - if xcert.EqualCertificatesFromPEM(certPEM, certRecord.Certificate) { + if xcert.EqualCertificatesFromPEM(certPEM, certItem.Certificate) { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certRecord.ID, - CertName: certRecord.Name, + CertId: certItem.ID, + CertName: certItem.Name, }, nil } } diff --git a/pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go b/pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go index ebe8ee406..04a1b88fd 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go @@ -65,8 +65,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10838&data=173&isNormal=1&vid=166 - queryCertListPage := int32(1) - queryCertListPerPage := int32(1000) + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10837&data=173&isNormal=1&vid=166 + queryCertListPage := 1 + queryCertListPerPage := 1000 for { select { case <-ctx.Done(): @@ -75,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } queryCertListReq := &ctyunicdn.QueryCertListRequest{ - Page: lo.ToPtr(queryCertListPage), - PerPage: lo.ToPtr(queryCertListPerPage), + Page: lo.ToPtr(int32(queryCertListPage)), + PerPage: lo.ToPtr(int32(queryCertListPerPage)), UsageMode: lo.ToPtr(int32(0)), } queryCertListResp, err := m.sdkClient.QueryCertList(queryCertListReq) @@ -85,55 +86,55 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryCertList': %w", err) } - if queryCertListResp.ReturnObj != nil { - for _, certRecord := range queryCertListResp.ReturnObj.Results { - // 对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) { - continue - } - - // 对比证书扩展名称 - if !slices.Equal(certX509.DNSNames, certRecord.SANs) { - continue - } - - // 对比证书有效期 - if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) { - continue - } else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) { - continue - } - - // 最后对比证书内容 - // 查询证书详情 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10837&data=173&isNormal=1&vid=166 - queryCertDetailReq := &ctyunicdn.QueryCertDetailRequest{ - Id: lo.ToPtr(certRecord.Id), - } - queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) - m.logger.Debug("sdk request 'icdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryCertDetail': %w", err) - } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { - if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), - CertName: queryCertDetailResp.ReturnObj.Result.Name, - }, nil - } - } - - if queryCertListResp.ReturnObj == nil || len(queryCertListResp.ReturnObj.Results) < int(queryCertListPerPage) { + if queryCertListResp.ReturnObj == nil { break - } else { - queryCertListPage++ } + + for _, certItem := range queryCertListResp.ReturnObj.Results { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CN) { + continue + } + + // 对比证书扩展名称 + if !slices.Equal(certX509.DNSNames, certItem.SANs) { + continue + } + + // 对比证书有效期 + if !certX509.NotBefore.Equal(time.Unix(certItem.IssueTime, 0).UTC()) { + continue + } else if !certX509.NotAfter.Equal(time.Unix(certItem.ExpiresTime, 0).UTC()) { + continue + } + + // 对比证书内容 + queryCertDetailReq := &ctyunicdn.QueryCertDetailRequest{ + Id: lo.ToPtr(certItem.Id), + } + queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) + m.logger.Debug("sdk request 'icdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryCertDetail': %w", err) + } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { + if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), + CertName: queryCertDetailResp.ReturnObj.Result.Name, + }, nil + } + + if len(queryCertListResp.ReturnObj.Results) < queryCertListPerPage { + break + } + + queryCertListPage++ } // 生成新证书名(需符合天翼云命名规则) diff --git a/pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go b/pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go index dafa4c034..73066322a 100644 --- a/pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go +++ b/pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go @@ -65,8 +65,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11452&data=183&isNormal=1&vid=261 - queryCertListPage := int32(1) - queryCertListPerPage := int32(1000) + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11449&data=183&isNormal=1&vid=261 + queryCertListPage := 1 + queryCertListPerPage := 1000 for { select { case <-ctx.Done(): @@ -75,8 +76,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } queryCertListReq := &ctyunlvdn.QueryCertListRequest{ - Page: lo.ToPtr(queryCertListPage), - PerPage: lo.ToPtr(queryCertListPerPage), + Page: lo.ToPtr(int32(queryCertListPage)), + PerPage: lo.ToPtr(int32(queryCertListPerPage)), UsageMode: lo.ToPtr(int32(0)), } queryCertListResp, err := m.sdkClient.QueryCertList(queryCertListReq) @@ -85,55 +86,55 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryCertList': %w", err) } - if queryCertListResp.ReturnObj != nil { - for _, certRecord := range queryCertListResp.ReturnObj.Results { - // 对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) { - continue - } - - // 对比证书扩展名称 - if !slices.Equal(certX509.DNSNames, certRecord.SANs) { - continue - } - - // 对比证书有效期 - if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) { - continue - } else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) { - continue - } - - // 最后对比证书内容 - // 查询证书详情 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11449&data=183&isNormal=1&vid=261 - queryCertDetailReq := &ctyunlvdn.QueryCertDetailRequest{ - Id: lo.ToPtr(certRecord.Id), - } - queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) - m.logger.Debug("sdk request 'lvdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryCertDetail': %w", err) - } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { - if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), - CertName: queryCertDetailResp.ReturnObj.Result.Name, - }, nil - } - } - - if queryCertListResp.ReturnObj == nil || len(queryCertListResp.ReturnObj.Results) < int(queryCertListPerPage) { + if queryCertListResp.ReturnObj == nil { break - } else { - queryCertListPage++ } + + for _, certItem := range queryCertListResp.ReturnObj.Results { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CN) { + continue + } + + // 对比证书扩展名称 + if !slices.Equal(certX509.DNSNames, certItem.SANs) { + continue + } + + // 对比证书有效期 + if !certX509.NotBefore.Equal(time.Unix(certItem.IssueTime, 0).UTC()) { + continue + } else if !certX509.NotAfter.Equal(time.Unix(certItem.ExpiresTime, 0).UTC()) { + continue + } + + // 对比证书内容 + queryCertDetailReq := &ctyunlvdn.QueryCertDetailRequest{ + Id: lo.ToPtr(certItem.Id), + } + queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq) + m.logger.Debug("sdk request 'lvdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryCertDetail': %w", err) + } else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil { + if !xcert.EqualCertificatesFromPEM(certPEM, queryCertDetailResp.ReturnObj.Result.Certs) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id), + CertName: queryCertDetailResp.ReturnObj.Result.Name, + }, nil + } + + if len(queryCertListResp.ReturnObj.Results) < queryCertListPerPage { + break + } + + queryCertListPage++ } // 生成新证书名(需符合天翼云命名规则) diff --git a/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go b/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go index de1d13fc4..7ce04cd81 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-elb/huaweicloud_elb.go @@ -69,8 +69,7 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { // 查询已有证书,避免重复上传 // REF: https://support.huaweicloud.com/api-elb/ListCertificates.html - listCertificatesLimit := int32(2000) - var listCertificatesMarker *string = nil + listCertificatesMarker := (*string)(nil) for { select { case <-ctx.Done(): @@ -79,9 +78,9 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } listCertificatesReq := &hcelbmodel.ListCertificatesRequest{ - Limit: lo.ToPtr(listCertificatesLimit), Marker: listCertificatesMarker, - Type: &[]string{"server"}, + Limit: lo.ToPtr(int32(2000)), + Type: lo.ToPtr([]string{"server"}), } listCertificatesResp, err := m.sdkClient.ListCertificates(listCertificatesReq) m.logger.Debug("sdk request 'elb.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp)) @@ -89,24 +88,26 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err) } - if listCertificatesResp.Certificates != nil { - for _, certInfo := range *listCertificatesResp.Certificates { - // 如果已存在相同证书,直接返回 - if xcert.EqualCertificatesFromPEM(certPEM, certInfo.Certificate) { - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: certInfo.Id, - CertName: certInfo.Name, - }, nil - } + if listCertificatesResp.Certificates == nil { + break + } + + for _, certItem := range *listCertificatesResp.Certificates { + // 如果已存在相同证书,直接返回 + if xcert.EqualCertificatesFromPEM(certPEM, certItem.Certificate) { + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.Id, + CertName: certItem.Name, + }, nil } } - if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) { + if len(*listCertificatesResp.Certificates) == 0 || listCertificatesResp.PageInfo.NextMarker == nil { break - } else { - listCertificatesMarker = listCertificatesResp.PageInfo.NextMarker } + + listCertificatesMarker = listCertificatesResp.PageInfo.NextMarker } // 获取项目 ID diff --git a/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go b/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go index 9d815d336..a2cb650ae 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-scm/huaweicloud_scm.go @@ -73,8 +73,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询已有证书,避免重复上传 // REF: https://support.huaweicloud.com/api-ccm/ListCertificates.html // REF: https://support.huaweicloud.com/api-ccm/ExportCertificate_0.html - listCertificatesLimit := int32(50) - listCertificatesOffset := int32(0) + listCertificatesLimit := 50 + listCertificatesOffset := 0 for { select { case <-ctx.Done(): @@ -84,8 +84,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listCertificatesReq := &hcscmmodel.ListCertificatesRequest{ EnterpriseProjectId: lo.EmptyableToPtr(m.config.EnterpriseProjectId), - Limit: lo.ToPtr(listCertificatesLimit), - Offset: lo.ToPtr(listCertificatesOffset), + Limit: lo.ToPtr(int32(listCertificatesLimit)), + Offset: lo.ToPtr(int32(listCertificatesOffset)), SortDir: lo.ToPtr("DESC"), SortKey: lo.ToPtr("certExpiredTime"), } @@ -95,51 +95,51 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'scm.ListCertificates': %w", err) } - if listCertificatesResp.Certificates != nil { - for _, certDetail := range *listCertificatesResp.Certificates { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certDetail.Domain) { - continue - } - - // 再对比证书有效期 - if certX509.NotAfter.Local().Format(time.DateTime) != strings.TrimSuffix(certDetail.ExpireTime, ".0") { - continue - } - - // 最后对比证书内容 - // 导出证书 - // REF: https://support.huaweicloud.com/api-ccm/ExportCertificate_0.html - exportCertificateReq := &hcscmmodel.ExportCertificateRequest{ - CertificateId: certDetail.Id, - } - exportCertificateResp, err := m.sdkClient.ExportCertificate(exportCertificateReq) - m.logger.Debug("sdk request 'scm.ExportCertificate'", slog.Any("request", exportCertificateReq), slog.Any("response", exportCertificateResp)) - if err != nil { - if exportCertificateResp != nil && exportCertificateResp.HttpStatusCode == 404 { - continue - } - return nil, fmt.Errorf("failed to execute sdk request 'scm.ExportCertificate': %w", err) - } else { - if !xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(exportCertificateResp.Certificate)) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: certDetail.Id, - CertName: certDetail.Name, - }, nil - } - } - - if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) { + if listCertificatesResp.Certificates == nil { break - } else { - listCertificatesOffset += listCertificatesLimit } + + for _, certItem := range *listCertificatesResp.Certificates { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.Domain) { + continue + } + + // 对比证书有效期 + if certX509.NotAfter.Local().Format(time.DateTime) != strings.TrimSuffix(certItem.ExpireTime, ".0") { + continue + } + + // 对比证书内容 + exportCertificateReq := &hcscmmodel.ExportCertificateRequest{ + CertificateId: certItem.Id, + } + exportCertificateResp, err := m.sdkClient.ExportCertificate(exportCertificateReq) + m.logger.Debug("sdk request 'scm.ExportCertificate'", slog.Any("request", exportCertificateReq), slog.Any("response", exportCertificateResp)) + if err != nil { + if exportCertificateResp != nil && exportCertificateResp.HttpStatusCode == 404 { + continue + } + return nil, fmt.Errorf("failed to execute sdk request 'scm.ExportCertificate': %w", err) + } else { + if !xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(exportCertificateResp.Certificate)) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.Id, + CertName: certItem.Name, + }, nil + } + + if len(*listCertificatesResp.Certificates) < listCertificatesLimit { + break + } + + listCertificatesOffset += listCertificatesLimit } // 生成新证书名(需符合华为云命名规则) diff --git a/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go b/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go index 06522586b..13f5014af 100644 --- a/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/pkg/core/ssl-manager/providers/huaweicloud-waf/huaweicloud_waf.go @@ -70,8 +70,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询已有证书,避免重复上传 // REF: https://support.huaweicloud.com/api-waf/ListCertificates.html // REF: https://support.huaweicloud.com/api-waf/ShowCertificate.html - listCertificatesPage := int32(1) - listCertificatesPageSize := int32(100) + listCertificatesPage := 1 + listCertificatesPageSize := 100 for { select { case <-ctx.Done(): @@ -81,8 +81,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey listCertificatesReq := &hcwafmodel.ListCertificatesRequest{ EnterpriseProjectId: lo.EmptyableToPtr(m.config.EnterpriseProjectId), - Page: lo.ToPtr(listCertificatesPage), - Pagesize: lo.ToPtr(listCertificatesPageSize), + Page: lo.ToPtr(int32(listCertificatesPage)), + Pagesize: lo.ToPtr(int32(listCertificatesPageSize)), } listCertificatesResp, err := m.sdkClient.ListCertificates(listCertificatesReq) m.logger.Debug("sdk request 'waf.ShowCertificate'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp)) @@ -90,34 +90,36 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'waf.ListCertificates': %w", err) } - if listCertificatesResp.Items != nil { - for _, certItem := range *listCertificatesResp.Items { - showCertificateReq := &hcwafmodel.ShowCertificateRequest{ - EnterpriseProjectId: lo.EmptyableToPtr(m.config.EnterpriseProjectId), - CertificateId: certItem.Id, - } - showCertificateResp, err := m.sdkClient.ShowCertificate(showCertificateReq) - m.logger.Debug("sdk request 'waf.ShowCertificate'", slog.Any("request", showCertificateReq), slog.Any("response", showCertificateResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'waf.ShowCertificate': %w", err) - } + if listCertificatesResp.Items == nil { + break + } - // 如果已存在相同证书,直接返回 - if xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(showCertificateResp.Content)) { - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: certItem.Id, - CertName: certItem.Name, - }, nil - } + for _, certItem := range *listCertificatesResp.Items { + showCertificateReq := &hcwafmodel.ShowCertificateRequest{ + EnterpriseProjectId: lo.EmptyableToPtr(m.config.EnterpriseProjectId), + CertificateId: certItem.Id, + } + showCertificateResp, err := m.sdkClient.ShowCertificate(showCertificateReq) + m.logger.Debug("sdk request 'waf.ShowCertificate'", slog.Any("request", showCertificateReq), slog.Any("response", showCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'waf.ShowCertificate': %w", err) + } + + // 如果已存在相同证书,直接返回 + if xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(showCertificateResp.Content)) { + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: certItem.Id, + CertName: certItem.Name, + }, nil } } - if listCertificatesResp.Items == nil || len(*listCertificatesResp.Items) < int(listCertificatesPageSize) { + if len(*listCertificatesResp.Items) < listCertificatesPageSize { break - } else { - listCertificatesPage++ } + + listCertificatesPage++ } // 生成新证书名(需符合华为云命名规则) diff --git a/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go b/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go index 1a496bf98..9e913d8ce 100644 --- a/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go +++ b/pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go @@ -92,36 +92,36 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeCerts': %w", err) } - for _, certDetail := range describeCertsResp.Result.CertListDetails { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, certDetail.CommonName) { + for _, certItem := range describeCertsResp.Result.CertListDetails { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certItem.CommonName) { continue } - // 再对比证书多域名 - if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), strings.Join(certDetail.DnsNames, ",")) { + // 对比证书多域名 + if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), strings.Join(certItem.DnsNames, ",")) { continue } - // 再对比证书有效期 - oldCertNotBefore, _ := time.Parse(time.RFC3339, certDetail.StartTime) - oldCertNotAfter, _ := time.Parse(time.RFC3339, certDetail.EndTime) + // 对比证书有效期 + oldCertNotBefore, _ := time.Parse(time.RFC3339, certItem.StartTime) + oldCertNotAfter, _ := time.Parse(time.RFC3339, certItem.EndTime) if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { continue } - // 最后对比私钥摘要 + // 对比私钥 SHA-256 摘要 newKeyDigest := sha256.Sum256([]byte(privkeyPEM)) newKeyDigestHex := hex.EncodeToString(newKeyDigest[:]) - if !strings.EqualFold(newKeyDigestHex, certDetail.Digest) { + if !strings.EqualFold(newKeyDigestHex, certItem.Digest) { continue } // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certDetail.CertId, - CertName: certDetail.CertName, + CertId: certItem.CertId, + CertName: certItem.CertName, }, nil } diff --git a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go index dd0297ded..cc6278289 100644 --- a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go +++ b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go @@ -69,7 +69,6 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询已有证书,避免重复上传 getSslCertListMarker := "" - getSslCertListLimit := int32(200) for { select { case <-ctx.Done(): @@ -77,62 +76,60 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey default: } - getSslCertListResp, err := m.sdkClient.GetSslCertList(context.TODO(), getSslCertListMarker, getSslCertListLimit) + getSslCertListResp, err := m.sdkClient.GetSslCertList(context.TODO(), getSslCertListMarker, 200) m.logger.Debug("sdk request 'sslcert.GetList'", slog.Any("request.marker", getSslCertListMarker), slog.Any("response", getSslCertListResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcert.GetList': %w", err) } - if getSslCertListResp.Certs != nil { - for _, sslCert := range getSslCertListResp.Certs { - // 先对比证书通用名称 - if !strings.EqualFold(certX509.Subject.CommonName, sslCert.CommonName) { - continue - } - - // 再对比证书多域名 - if !slices.Equal(certX509.DNSNames, sslCert.DnsNames) { - continue - } - - // 再对比证书有效期 - if certX509.NotBefore.Unix() != sslCert.NotBefore || certX509.NotAfter.Unix() != sslCert.NotAfter { - continue - } - - // 最后对比证书公钥算法 - switch certX509.PublicKeyAlgorithm { - case x509.RSA: - if !strings.EqualFold(sslCert.Encrypt, "RSA") { - continue - } - case x509.ECDSA: - if !strings.EqualFold(sslCert.Encrypt, "ECDSA") { - continue - } - case x509.Ed25519: - if !strings.EqualFold(sslCert.Encrypt, "ED25519") { - continue - } - default: - // 未知算法,跳过 - continue - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: sslCert.CertID, - CertName: sslCert.Name, - }, nil + for _, sslItem := range getSslCertListResp.Certs { + // 对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, sslItem.CommonName) { + continue } + + // 对比证书多域名 + if !slices.Equal(certX509.DNSNames, sslItem.DnsNames) { + continue + } + + // 对比证书有效期 + if certX509.NotBefore.Unix() != sslItem.NotBefore || certX509.NotAfter.Unix() != sslItem.NotAfter { + continue + } + + // 对比证书公钥算法 + switch certX509.PublicKeyAlgorithm { + case x509.RSA: + if !strings.EqualFold(sslItem.Encrypt, "RSA") { + continue + } + case x509.ECDSA: + if !strings.EqualFold(sslItem.Encrypt, "ECDSA") { + continue + } + case x509.Ed25519: + if !strings.EqualFold(sslItem.Encrypt, "ED25519") { + continue + } + default: + // 未知算法,跳过 + continue + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: sslItem.CertID, + CertName: sslItem.Name, + }, nil } - if len(getSslCertListResp.Certs) < int(getSslCertListLimit) || getSslCertListResp.Marker == "" { + if len(getSslCertListResp.Certs) == 0 || getSslCertListResp.Marker == "" { break - } else { - getSslCertListMarker = getSslCertListResp.Marker } + + getSslCertListMarker = getSslCertListResp.Marker } // 上传新证书 diff --git a/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go b/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go index 8170befe2..66dd537d5 100644 --- a/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go +++ b/pkg/core/ssl-manager/providers/rainyun-sslcenter/rainyun_sslcenter.go @@ -10,6 +10,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" rainyunsdk "github.com/certimate-go/certimate/pkg/sdk3rd/rainyun" xcert "github.com/certimate-go/certimate/pkg/utils/cert" + "github.com/samber/lo" ) type SSLManagerProviderConfig struct { @@ -52,7 +53,7 @@ func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) { func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) { // 避免重复上传 - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res != nil { m.logger.Info("ssl certificate already exists") @@ -72,16 +73,16 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } // 获取刚刚上传证书 ID - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res == nil { - return nil, errors.New("no ssl certificate found, may be upload failed") + return nil, errors.New("could not find ssl certificate, may be upload failed") } else { return res, nil } } -func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { +func (m *SSLManagerProvider) tryFindCert(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { // 解析证书内容 certX509, err := xcert.ParseCertificateFromPEM(certPEM) if err != nil { @@ -91,8 +92,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin // 获取 SSL 证书列表 // REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943046 // REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943048 - sslCenterListPage := int32(1) - sslCenterListPerPage := int32(100) + sslCenterListPage := 1 + sslCenterListPerPage := 100 for { select { case <-ctx.Done(): @@ -104,8 +105,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin Filters: &rainyunsdk.SslCenterListFilters{ Domain: &certX509.Subject.CommonName, }, - Page: &sslCenterListPage, - PerPage: &sslCenterListPerPage, + Page: lo.ToPtr(int32(sslCenterListPage)), + PerPage: lo.ToPtr(int32(sslCenterListPerPage)), } sslCenterListResp, err := m.sdkClient.SslCenterList(sslCenterListReq) m.logger.Debug("sdk request 'sslcenter.List'", slog.Any("request", sslCenterListReq), slog.Any("response", sslCenterListResp)) @@ -113,40 +114,42 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.List': %w", err) } - if sslCenterListResp.Data != nil && sslCenterListResp.Data.Records != nil { - for _, sslRecord := range sslCenterListResp.Data.Records { - // 先对比证书的多域名 - if sslRecord.Domain != strings.Join(certX509.DNSNames, ", ") { - continue - } - - // 再对比证书的有效期 - if sslRecord.StartDate != certX509.NotBefore.Unix() || sslRecord.ExpireDate != certX509.NotAfter.Unix() { - continue - } - - // 最后对比证书内容 - sslCenterGetResp, err := m.sdkClient.SslCenterGet(sslRecord.ID) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.Get': %w", err) - } else { - if !xcert.EqualCertificatesFromPEM(certPEM, sslCenterGetResp.Data.Cert) { - continue - } - } - - // 如果以上信息都一致,则视为已存在相同证书,直接返回 - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", sslRecord.ID), - }, nil - } - } - - if sslCenterListResp.Data == nil || len(sslCenterListResp.Data.Records) < int(sslCenterListPerPage) { + if sslCenterListResp.Data == nil { break - } else { - sslCenterListPage++ } + + for _, sslItem := range sslCenterListResp.Data.Records { + // 对比证书的多域名 + if sslItem.Domain != strings.Join(certX509.DNSNames, ", ") { + continue + } + + // 对比证书的有效期 + if sslItem.StartDate != certX509.NotBefore.Unix() || sslItem.ExpireDate != certX509.NotAfter.Unix() { + continue + } + + // 对比证书内容 + sslCenterGetResp, err := m.sdkClient.SslCenterGet(sslItem.ID) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.Get': %w", err) + } else { + if !xcert.EqualCertificatesFromPEM(certPEM, sslCenterGetResp.Data.Cert) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", sslItem.ID), + }, nil + } + + if len(sslCenterListResp.Data.Records) < sslCenterListPerPage { + break + } + + sslCenterListPage++ } return nil, nil diff --git a/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go b/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go index 79a485326..2d7d590ba 100644 --- a/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go +++ b/pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go @@ -86,10 +86,10 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey m.logger.Debug("sdk request 'ussl.UploadNormalCertificate'", slog.Any("request", uploadNormalCertificateReq), slog.Any("response", uploadNormalCertificateResp)) if err != nil { if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 { - if res, err := m.findCertIfExists(ctx, certPEM); err != nil { + if res, err := m.tryFindCert(ctx, certPEM); err != nil { return nil, err } else if res == nil { - return nil, errors.New("ucloud ssl: no certificate found") + return nil, errors.New("could not find ssl certificate, may be upload failed") } else { m.logger.Info("ssl certificate already exists") return res, nil @@ -108,7 +108,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey }, nil } -func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { +func (m *SSLManagerProvider) tryFindCert(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) { // 解析证书内容 certX509, err := xcert.ParseCertificateFromPEM(certPEM) if err != nil { @@ -118,8 +118,8 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin // 查询用户证书列表 // REF: https://docs.ucloud.cn/api/usslcertificate-api/get_certificate_list // REF: https://docs.ucloud.cn/api/usslcertificate-api/download_certificate - getCertificateListPage := int(1) - getCertificateListLimit := int(1000) + getCertificateListPage := 1 + getCertificateListLimit := 1000 for { select { case <-ctx.Done(): @@ -142,90 +142,88 @@ func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM strin return nil, fmt.Errorf("failed to execute sdk request 'ussl.GetCertificateList': %w", err) } - if getCertificateListResp.CertificateList != nil { - for _, certItem := range getCertificateListResp.CertificateList { - // 优刻得未提供可唯一标识证书的字段,只能通过多个字段尝试对比来判断是否为同一证书 - // 先分别对比证书的多域名、品牌、有效期,再对比签名算法 + for _, certItem := range getCertificateListResp.CertificateList { + // 优刻得未提供可唯一标识证书的字段,只能通过多个字段尝试对比来判断是否为同一证书 + // 先分别对比证书的多域名、品牌、有效期,再对比签名算法 - if len(certX509.DNSNames) == 0 || certItem.Domains != strings.Join(certX509.DNSNames, ",") { - continue - } - - if len(certX509.Issuer.Organization) == 0 || certItem.Brand != certX509.Issuer.Organization[0] { - continue - } - - if int64(certItem.NotBefore) != certX509.NotBefore.UnixMilli() || int64(certItem.NotAfter) != certX509.NotAfter.UnixMilli() { - continue - } - - getCertificateDetailInfoReq := m.sdkClient.NewGetCertificateDetailInfoRequest() - getCertificateDetailInfoReq.CertificateID = ucloud.Int(certItem.CertificateID) - if m.config.ProjectId != "" { - getCertificateDetailInfoReq.ProjectId = ucloud.String(m.config.ProjectId) - } - getCertificateDetailInfoResp, err := m.sdkClient.GetCertificateDetailInfo(getCertificateDetailInfoReq) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'ussl.GetCertificateDetailInfo': %w", err) - } - - switch certX509.SignatureAlgorithm { - case x509.SHA256WithRSA: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSA") { - continue - } - case x509.SHA384WithRSA: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSA") { - continue - } - case x509.SHA512WithRSA: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSA") { - continue - } - case x509.SHA256WithRSAPSS: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSAPSS") { - continue - } - case x509.SHA384WithRSAPSS: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSAPSS") { - continue - } - case x509.SHA512WithRSAPSS: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSAPSS") { - continue - } - case x509.ECDSAWithSHA256: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA256") { - continue - } - case x509.ECDSAWithSHA384: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA384") { - continue - } - case x509.ECDSAWithSHA512: - if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA512") { - continue - } - default: - // 未知签名算法,跳过 - continue - } - - return &core.SSLManageUploadResult{ - CertId: fmt.Sprintf("%d", certItem.CertificateID), - CertName: certItem.Name, - ExtendedData: map[string]any{ - "ResourceId": certItem.CertificateSN, - }, - }, nil + if len(certX509.DNSNames) == 0 || certItem.Domains != strings.Join(certX509.DNSNames, ",") { + continue } + + if len(certX509.Issuer.Organization) == 0 || certItem.Brand != certX509.Issuer.Organization[0] { + continue + } + + if int64(certItem.NotBefore) != certX509.NotBefore.UnixMilli() || int64(certItem.NotAfter) != certX509.NotAfter.UnixMilli() { + continue + } + + getCertificateDetailInfoReq := m.sdkClient.NewGetCertificateDetailInfoRequest() + getCertificateDetailInfoReq.CertificateID = ucloud.Int(certItem.CertificateID) + if m.config.ProjectId != "" { + getCertificateDetailInfoReq.ProjectId = ucloud.String(m.config.ProjectId) + } + getCertificateDetailInfoResp, err := m.sdkClient.GetCertificateDetailInfo(getCertificateDetailInfoReq) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ussl.GetCertificateDetailInfo': %w", err) + } + + switch certX509.SignatureAlgorithm { + case x509.SHA256WithRSA: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSA") { + continue + } + case x509.SHA384WithRSA: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSA") { + continue + } + case x509.SHA512WithRSA: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSA") { + continue + } + case x509.SHA256WithRSAPSS: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSAPSS") { + continue + } + case x509.SHA384WithRSAPSS: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSAPSS") { + continue + } + case x509.SHA512WithRSAPSS: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSAPSS") { + continue + } + case x509.ECDSAWithSHA256: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA256") { + continue + } + case x509.ECDSAWithSHA384: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA384") { + continue + } + case x509.ECDSAWithSHA512: + if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA512") { + continue + } + default: + // 未知签名算法,跳过 + continue + } + + return &core.SSLManageUploadResult{ + CertId: fmt.Sprintf("%d", certItem.CertificateID), + CertName: certItem.Name, + ExtendedData: map[string]any{ + "ResourceId": certItem.CertificateSN, + }, + }, nil } - if getCertificateListResp.CertificateList == nil || len(getCertificateListResp.CertificateList) < int(getCertificateListLimit) { + if len(getCertificateListResp.CertificateList) < getCertificateListLimit { break - } else { - getCertificateListPage++ } + + getCertificateListPage++ } return nil, nil diff --git a/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go b/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go index 39acc4c71..5e3075a34 100644 --- a/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go @@ -69,14 +69,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 查询证书列表,避免重复上传 // REF: https://www.volcengine.com/docs/6454/125709 - listCertInfoPageNum := int32(1) - listCertInfoPageSize := int32(100) - listCertInfoTotal := 0 - listCertInfoReq := &vecdn.ListCertInfoInput{ - Source: ve.String("volc_cert_center"), - PageNum: ve.Int32(listCertInfoPageNum), - PageSize: ve.Int32(listCertInfoPageSize), - } + listCertInfoPageNum := 1 + listCertInfoPageSize := 100 for { select { case <-ctx.Done(): @@ -84,40 +78,43 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey default: } + listCertInfoReq := &vecdn.ListCertInfoInput{ + Source: ve.String("volc_cert_center"), + PageNum: ve.Int32(int32(listCertInfoPageNum)), + PageSize: ve.Int32(int32(listCertInfoPageSize)), + } listCertInfoResp, err := m.sdkClient.ListCertInfo(listCertInfoReq) m.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCertInfo': %w", err) } - if listCertInfoResp.CertInfo != nil { - for _, certInfo := range listCertInfoResp.CertInfo { - fingerprintSha1 := sha1.Sum(certX509.Raw) - if !strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), ve.StringValue(certInfo.CertFingerprint.Sha1)) { - continue - } - - fingerprintSha256 := sha256.Sum256(certX509.Raw) - if !strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), ve.StringValue(certInfo.CertFingerprint.Sha256)) { - continue - } - - // 如果已存在相同证书,直接返回 - m.logger.Info("ssl certificate already exists") - return &core.SSLManageUploadResult{ - CertId: ve.StringValue(certInfo.CertId), - CertName: ve.StringValue(certInfo.Desc), - }, nil + for _, certItem := range listCertInfoResp.CertInfo { + // 对比证书 SHA-1 摘要 + fingerprintSha1 := sha1.Sum(certX509.Raw) + if !strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), ve.StringValue(certItem.CertFingerprint.Sha1)) { + continue } + + // 对比证书 SHA-256 摘要 + fingerprintSha256 := sha256.Sum256(certX509.Raw) + if !strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), ve.StringValue(certItem.CertFingerprint.Sha256)) { + continue + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + m.logger.Info("ssl certificate already exists") + return &core.SSLManageUploadResult{ + CertId: ve.StringValue(certItem.CertId), + CertName: ve.StringValue(certItem.Desc), + }, nil } - listCertInfoLen := len(listCertInfoResp.CertInfo) - if listCertInfoLen < int(listCertInfoPageSize) || int(ve.Int64Value(listCertInfoResp.Total)) <= listCertInfoTotal+listCertInfoLen { + if len(listCertInfoResp.CertInfo) < listCertInfoPageSize { break - } else { - listCertInfoPageNum++ - listCertInfoTotal += listCertInfoLen } + + listCertInfoPageNum++ } // 生成新证书名(需符合火山引擎命名规则) diff --git a/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go b/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go index 284403cb4..fde52ef3b 100644 --- a/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-manager/providers/volcengine-live/volcengine_live.go @@ -64,11 +64,11 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey return nil, fmt.Errorf("failed to execute sdk request 'live.ListCertV2': %w", err) } if listCertResp.Result.CertList != nil { - for _, certInfo := range listCertResp.Result.CertList { + for _, certItem := range listCertResp.Result.CertList { // 查询证书详细信息 // REF: https://www.volcengine.com/docs/6469/1186278#%E6%9F%A5%E7%9C%8B%E8%AF%81%E4%B9%A6%E8%AF%A6%E6%83%85 describeCertDetailSecretReq := &velive.DescribeCertDetailSecretV2Body{ - ChainID: ve.String(certInfo.ChainID), + ChainID: ve.String(certItem.ChainID), } describeCertDetailSecretResp, err := m.sdkClient.DescribeCertDetailSecretV2(ctx, describeCertDetailSecretReq) m.logger.Debug("sdk request 'live.DescribeCertDetailSecretV2'", slog.Any("request", describeCertDetailSecretReq), slog.Any("response", describeCertDetailSecretResp)) @@ -81,8 +81,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if xcert.EqualCertificatesFromPEM(certPEM, oldCertPEM) { m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certInfo.ChainID, - CertName: certInfo.CertName, + CertId: certItem.ChainID, + CertName: certItem.CertName, }, nil } } diff --git a/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go b/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go index d74829aff..73441998d 100644 --- a/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go +++ b/pkg/core/ssl-manager/providers/wangsu-certificate/wangsu_certificate.go @@ -72,16 +72,16 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } if listCertificatesResp.Certificates != nil { - for _, certRecord := range listCertificatesResp.Certificates { + for _, certItem := range listCertificatesResp.Certificates { // 对比证书序列号 - if !strings.EqualFold(certX509.SerialNumber.Text(16), certRecord.Serial) { + if !strings.EqualFold(certX509.SerialNumber.Text(16), certItem.Serial) { continue } - // 再对比证书有效期 - cstzone := time.FixedZone("CST", 8*60*60) - oldCertNotBefore, _ := time.ParseInLocation(time.DateTime, certRecord.ValidityFrom, cstzone) - oldCertNotAfter, _ := time.ParseInLocation(time.DateTime, certRecord.ValidityTo, cstzone) + // 对比证书有效期 + timezoneOfCST := time.FixedZone("CST", 8*60*60) + oldCertNotBefore, _ := time.ParseInLocation(time.DateTime, certItem.ValidityFrom, timezoneOfCST) + oldCertNotAfter, _ := time.ParseInLocation(time.DateTime, certItem.ValidityTo, timezoneOfCST) if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { continue } @@ -89,8 +89,8 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 如果以上信息都一致,则视为已存在相同证书,直接返回 m.logger.Info("ssl certificate already exists") return &core.SSLManageUploadResult{ - CertId: certRecord.CertificateId, - CertName: certRecord.Name, + CertId: certItem.CertificateId, + CertName: certItem.Name, }, nil } } diff --git a/pkg/sdk3rd/baishan/api_get_domain_list.go b/pkg/sdk3rd/baishan/api_get_domain_list.go new file mode 100644 index 000000000..f4c5166b0 --- /dev/null +++ b/pkg/sdk3rd/baishan/api_get_domain_list.go @@ -0,0 +1,55 @@ +package baishan + +import ( + "context" + "encoding/json" + "fmt" + "net/http" +) + +type GetDomainListRequest struct { + PageNumber *int32 `json:"page_number,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + DomainStatus *string `json:"domain_status,omitempty"` +} + +type GetDomainListResponse struct { + apiResponseBase + + Data []*struct { + List []*DomainRecord `json:"list"` + PageNumber json.Number `json:"page_number"` + PageSize json.Number `json:"page_size"` + TotalNumber json.Number `json:"total_number"` + } `json:"data,omitempty"` +} + +func (c *Client) GetDomainList(req *GetDomainListRequest) (*GetDomainListResponse, error) { + return c.GetDomainListWithContext(context.Background(), req) +} + +func (c *Client) GetDomainListWithContext(ctx context.Context, req *GetDomainListRequest) (*GetDomainListResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v2/domain/list") + if err != nil { + return nil, err + } else { + if req.PageNumber != nil { + httpreq.SetQueryParam("page_number", fmt.Sprintf("%d", *req.PageNumber)) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_number", fmt.Sprintf("%d", *req.PageSize)) + } + if req.DomainStatus != nil { + httpreq.SetQueryParam("domain_status", *req.DomainStatus) + } + + httpreq.SetContext(ctx) + } + + result := &GetDomainListResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/baishan/types.go b/pkg/sdk3rd/baishan/types.go index b6bda59f7..c06e859b8 100644 --- a/pkg/sdk3rd/baishan/types.go +++ b/pkg/sdk3rd/baishan/types.go @@ -30,6 +30,17 @@ func (r *apiResponseBase) GetMessage() string { var _ apiResponse = (*apiResponseBase)(nil) +type DomainRecord struct { + Id string `json:"id"` + Domain string `json:"domain"` + Type string `json:"type"` + Status string `json:"status"` + Cname string `json:"cname"` + Area string `json:"area"` + CreateTime string `json:"create_time"` + UpdateTime string `json:"update_time"` +} + type DomainCertificate struct { CertId json.Number `json:"cert_id"` Name string `json:"name"` diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaishanCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaishanCDN.tsx index beb9afdc0..721efb774 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaishanCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaishanCDN.tsx @@ -1,5 +1,5 @@ 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"; @@ -7,6 +7,8 @@ import { validDomainName } from "@/utils/validators"; import { useFormNestedFieldsContext } from "./_context"; +const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const; + const BizDeployNodeConfigFieldsProviderBaishanCDN = () => { const { i18n, t } = useTranslation(); @@ -15,10 +17,35 @@ const BizDeployNodeConfigFieldsProviderBaishanCDN = () => { [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 ( <> + + ) : ( + void 0 + ) + } + rules={[formRule]} + > + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + { const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -51,16 +79,35 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - certificateId: z - .union([z.string(), z.number().int()]) - .nullish() - .refine((v) => { - if (!v) return true; - return /^\d+$/.test(v + "") && +v > 0; - }, t("workflow_node.deploy.form.baishan_cdn_certificate_id.placeholder")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (!v) return true; + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.baishan_cdn_certificate_id.placeholder")), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderBaishanCDN, { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWangsuCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWangsuCDN.tsx index 164c8d9aa..bbc46c122 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWangsuCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWangsuCDN.tsx @@ -1,5 +1,5 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form } from "antd"; +import { Form, Radio } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -10,6 +10,8 @@ import { useFormNestedFieldsContext } from "./_context"; const MULTIPLE_INPUT_SEPARATOR = ";"; +const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const; + const BizDeployNodeConfigFieldsProviderWangsuCDN = () => { const { i18n, t } = useTranslation(); @@ -18,10 +20,35 @@ const BizDeployNodeConfigFieldsProviderWangsuCDN = () => { [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 ( <> + + ) : ( + void 0 + ) + } + rules={[formRule]} + > + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + { const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domains: "", }; }; @@ -50,14 +78,33 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domains: z.string().refine((v) => { - if (!v) return false; - return String(v) - .split(MULTIPLE_INPUT_SEPARATOR) - .every((e) => validDomainName(e, { allowWildcard: true })); - }, t("workflow_node.deploy.form.wangsu_cdn_domains.placeholder")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domains: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + const v = + values.domains && + String(values.domains) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => validDomainName(e, { allowWildcard: true })); + if (!v) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domains"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderWangsuCDN, { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWangsuCDNPro.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWangsuCDNPro.tsx index 5cf4fecc2..629d85feb 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWangsuCDNPro.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWangsuCDNPro.tsx @@ -1,5 +1,5 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form, Input, Select } from "antd"; +import { Form, Input, Radio, Select } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -10,6 +10,8 @@ import { useFormNestedFieldsContext } from "./_context"; const ENVIRONMENT_PRODUCTION = "production" as const; const ENVIRONMENT_STAGING = "stating" as const; +const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const; + const BizDeployNodeConfigFieldsProviderWangsuCDNPro = () => { const { i18n, t } = useTranslation(); @@ -18,8 +20,11 @@ const BizDeployNodeConfigFieldsProviderWangsuCDNPro = () => { [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 ( <> { + + ) : ( + void 0 + ) + } + rules={[formRule]} + > + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + { const getInitialValues = (): Nullish>> => { return { environment: ENVIRONMENT_PRODUCTION, + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -81,12 +109,31 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - environment: z.literal([ENVIRONMENT_PRODUCTION, ENVIRONMENT_STAGING], t("workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder")), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - certificateId: z.string().nullish(), - webhookId: z.string().nullish(), - }); + return z + .object({ + environment: z.literal([ENVIRONMENT_PRODUCTION, ENVIRONMENT_STAGING], t("workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + certificateId: z.string().nullish(), + webhookId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderWangsuCDNPro, { From 1356970dcdf3f2a9babd8044b6ddc20edb767887 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:03 +0800 Subject: [PATCH 13/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to qiniu cdn --- internal/certdeploy/deployers/sp_qiniu_cdn.go | 7 +- .../providers/qiniu-cdn/consts.go | 10 + .../providers/qiniu-cdn/qiniu_cdn.go | 175 +++++++++++++++--- .../providers/qiniu-cdn/qiniu_cdn_test.go | 7 +- pkg/sdk3rd/qiniu/cdn.go | 33 ++++ ...DeployNodeConfigFieldsProviderQiniuCDN.tsx | 69 ++++++- .../i18n/locales/zh/nls.workflow.nodes.json | 2 +- 7 files changed, 264 insertions(+), 39 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/qiniu-cdn/consts.go diff --git a/internal/certdeploy/deployers/sp_qiniu_cdn.go b/internal/certdeploy/deployers/sp_qiniu_cdn.go index e93f7a7e1..4fde037df 100644 --- a/internal/certdeploy/deployers/sp_qiniu_cdn.go +++ b/internal/certdeploy/deployers/sp_qiniu_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := qiniucdn.NewSSLDeployerProvider(&qiniucdn.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/qiniu-cdn/consts.go b/pkg/core/ssl-deployer/providers/qiniu-cdn/consts.go new file mode 100644 index 000000000..a4bbfb19e --- /dev/null +++ b/pkg/core/ssl-deployer/providers/qiniu-cdn/consts.go @@ -0,0 +1,10 @@ +package qiniucdn + +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/qiniu-cdn/qiniu_cdn.go b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go index a18816644..c97917b21 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go +++ b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go @@ -8,10 +8,13 @@ import ( "strings" "github.com/qiniu/go-sdk/v7/auth" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/qiniu-sslcert" qiniusdk "github.com/certimate-go/certimate/pkg/sdk3rd/qiniu" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -19,6 +22,9 @@ type SSLDeployerProviderConfig struct { AccessKey string `json:"accessKey"` // 七牛云 SecretKey。 SecretKey string `json:"secretKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -66,10 +72,6 @@ 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, fmt.Errorf("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -78,32 +80,157 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // "*.example.com" → ".example.com",适配七牛云 CDN 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } - // 获取域名信息 - // REF: https://developer.qiniu.com/fusion/4246/the-domain-name - getDomainInfoResp, err := d.sdkClient.GetDomainInfo(context.TODO(), domain) - d.logger.Debug("sdk request 'cdn.GetDomainInfo'", slog.String("request.domain", domain), slog.Any("response", getDomainInfoResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainInfo': %w", err) + // "*.example.com" → ".example.com",适配七牛云 CDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{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) || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any 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("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS - // REF: https://developer.qiniu.com/fusion/4246/the-domain-name - if getDomainInfoResp.Https == nil || getDomainInfoResp.Https.CertID == "" { - enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(context.TODO(), domain, upres.CertId, true, true) - d.logger.Debug("sdk request 'cdn.EnableDomainHttps'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", enableDomainHttpsResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.EnableDomainHttps': %w", err) + // 遍历更新域名证书 + 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, upres.CertId); err != nil { + errs = append(errs, err) + } + } } - } else if getDomainInfoResp.Https.CertID != upres.CertId { - modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(context.TODO(), domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) - d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", modifyDomainHttpsConfResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainHttpsConf': %w", 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://developer.qiniu.com/fusion/4246/the-domain-name + getDomainListMarker := "" + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + getDomainListResp, err := d.sdkClient.GetDomainList(context.TODO(), getDomainListMarker, 100) + d.logger.Debug("sdk request 'cdn.GetDomainList'", slog.String("request.marker", getDomainListMarker), slog.Any("response", getDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainList': %w", err) + } + + ignoredStatuses := []string{"frozen", "offlined"} + for _, domainItem := range getDomainListResp.Domains { + if lo.Contains(ignoredStatuses, domainItem.OperatingState) { + continue + } + + domains = append(domains, domainItem.Name) + } + + if len(getDomainListResp.Domains) == 0 || getDomainListResp.Marker == "" { + break + } + + getDomainListMarker = getDomainListResp.Marker + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 获取域名信息 + // REF: https://developer.qiniu.com/fusion/4246/the-domain-name + getDomainInfoResp, err := d.sdkClient.GetDomainInfo(context.TODO(), domain) + d.logger.Debug("sdk request 'cdn.GetDomainInfo'", slog.String("request.domain", domain), slog.Any("response", getDomainInfoResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.GetDomainInfo': %w", err) + } + + // 判断域名是否已启用 HTTPS + // 如果已启用,修改域名证书;否则,启用 HTTPS + // REF: https://developer.qiniu.com/fusion/4246/the-domain-name + if getDomainInfoResp.Https == nil || getDomainInfoResp.Https.CertID == "" { + enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(context.TODO(), domain, cloudCertId, true, true) + d.logger.Debug("sdk request 'cdn.EnableDomainHttps'", slog.String("request.domain", domain), slog.String("request.certId", cloudCertId), slog.Any("response", enableDomainHttpsResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.EnableDomainHttps': %w", err) + } + } else if getDomainInfoResp.Https.CertID != cloudCertId { + modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(context.TODO(), domain, cloudCertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) + d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("request.domain", domain), slog.String("request.certId", cloudCertId), slog.Any("response", modifyDomainHttpsConfResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainHttpsConf': %w", err) + } + } + + return nil +} diff --git a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn_test.go b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn_test.go index 5ad633c37..62682b6b0 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKey: fAccessKey, - SecretKey: fSecretKey, - Domain: fDomain, + AccessKey: fAccessKey, + SecretKey: fSecretKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/sdk3rd/qiniu/cdn.go b/pkg/sdk3rd/qiniu/cdn.go index 74745d0ba..d658bbeaa 100644 --- a/pkg/sdk3rd/qiniu/cdn.go +++ b/pkg/sdk3rd/qiniu/cdn.go @@ -2,7 +2,9 @@ package qiniu import ( "context" + "fmt" "net/http" + "net/url" "github.com/qiniu/go-sdk/v7/auth" "github.com/qiniu/go-sdk/v7/client" @@ -21,6 +23,37 @@ func NewCdnManager(mac *auth.Credentials) *CdnManager { return &CdnManager{client: client} } +type GetDomainListResponse struct { + Code *int `json:"code,omitempty"` + Error *string `json:"error,omitempty"` + Marker string `json:"marker"` + Domains []*struct { + Name string `json:"name"` + Type string `json:"type"` + CName string `json:"cname"` + OperatingState string `json:"operatingState"` + OperatingStateDesc string `json:"operatingStateDesc"` + CreateAt string `json:"createAt"` + ModifyAt string `json:"modifyAt"` + } `json:"domains"` +} + +func (m *CdnManager) GetDomainList(ctx context.Context, marker string, limit int) (*GetDomainListResponse, error) { + query := url.Values{} + if marker != "" { + query.Set("marker", marker) + } + if limit > 0 { + query.Set("limit", fmt.Sprintf("%d", limit)) + } + + resp := new(GetDomainListResponse) + if err := m.client.Call(ctx, resp, http.MethodGet, "domain?"+query.Encode(), nil); err != nil { + return nil, err + } + return resp, nil +} + type GetDomainInfoResponse struct { Code *int `json:"code,omitempty"` Error *string `json:"error,omitempty"` diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuCDN.tsx index bde644cc2..05156bd29 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuCDN.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 BizDeployNodeConfigFieldsProviderQiniuCDN = () => { const { i18n, t } = useTranslation(); @@ -15,24 +20,52 @@ const BizDeployNodeConfigFieldsProviderQiniuCDN = () => { [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 ( <> + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +73,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderQiniuCDN, { diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 03397829a..3b2f826e2 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -197,7 +197,7 @@ "workflow_node.deploy.form.shared_domain_match_pattern.option.exact.label": "精确匹配", "workflow_node.deploy.form.shared_domain_match_pattern.option.wildcard.label": "通配符匹配(泛域名)", "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "根据证书自动匹配", - "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "注意:对于泛解析的站点,精确匹配仅包含该泛解析站点本身、不包括相关子域名站点。", + "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "注意:对于泛解析的站点,泛域名的精确匹配仅包含该泛解析站点本身、不包括相关子域名站点。", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启 1Panel 服务", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel 子节点名称(可选)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "请输入 1Panel 子节点名称", From 713440aba1c50034a4159596886caec30eeea1b4 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:03 +0800 Subject: [PATCH 14/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to upyun cdn --- .../certdeploy/deployers/sp_rainyun_rcdn.go | 7 +- .../providers/aliyun-apigw/aliyun_apigw.go | 4 +- .../providers/aliyun-cdn/aliyun_cdn.go | 3 +- .../providers/aliyun-dcdn/aliyun_dcdn.go | 3 +- .../providers/jdcloud-cdn/jdcloud_cdn.go | 4 +- .../providers/qiniu-cdn/qiniu_cdn.go | 3 +- .../providers/rainyun-rcdn/consts.go | 6 + .../providers/rainyun-rcdn/rainyun_rcdn.go | 3 + .../rainyun-rcdn/rainyun_rcdn_test.go | 7 +- .../providers/upyun-cdn/consts.go | 10 + .../providers/upyun-cdn/upyun_cdn.go | 209 +++++++++++++++--- .../providers/wangsu-cdn/wangsu_cdn.go | 28 ++- pkg/sdk3rd/upyun/console/api_get_buckets.go | 85 +++++++ pkg/utils/cert/hostname/hostname.go | 7 +- ...loyNodeConfigFieldsProviderRainYunRCDN.tsx | 61 ++++- ...DeployNodeConfigFieldsProviderUpyunCDN.tsx | 69 +++++- .../i18n/locales/en/nls.workflow.nodes.json | 2 +- .../i18n/locales/zh/nls.workflow.nodes.json | 2 +- 18 files changed, 438 insertions(+), 75 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/rainyun-rcdn/consts.go create mode 100644 pkg/core/ssl-deployer/providers/upyun-cdn/consts.go create mode 100644 pkg/sdk3rd/upyun/console/api_get_buckets.go diff --git a/internal/certdeploy/deployers/sp_rainyun_rcdn.go b/internal/certdeploy/deployers/sp_rainyun_rcdn.go index 27d3c5330..6ae3bfbb1 100644 --- a/internal/certdeploy/deployers/sp_rainyun_rcdn.go +++ b/internal/certdeploy/deployers/sp_rainyun_rcdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := rainyunrcdn.NewSSLDeployerProvider(&rainyunrcdn.SSLDeployerProviderConfig{ - ApiKey: credentials.ApiKey, - InstanceId: xmaps.GetInt64(options.ProviderExtendedConfig, "instanceId"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + ApiKey: credentials.ApiKey, + InstanceId: xmaps.GetInt64(options.ProviderExtendedConfig, "instanceId"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go index 06a31052e..1415953f7 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -205,7 +205,7 @@ func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Contex listDomainsReq := &aliapig.ListDomainsRequest{ ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), GatewayId: tea.String(d.config.GatewayId), - NameLike: tea.String(d.config.Domain), + NameLike: tea.String(domain), PageNumber: tea.Int32(int32(listDomainsPageNumber)), PageSize: tea.Int32(int32(listDomainsPageSize)), } @@ -220,7 +220,7 @@ func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Contex } for _, domainItem := range listDomainsResp.Body.Data.Items { - if strings.EqualFold(tea.StringValue(domainItem.Name), d.config.Domain) { + if strings.EqualFold(tea.StringValue(domainItem.Name), domain) { return tea.StringValue(domainItem.DomainId), nil } } 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 74a0366c0..decda89be 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -144,7 +144,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { - return certX509.VerifyHostname(domain) == nil + return certX509.VerifyHostname(domain) == nil || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") }) if len(domains) == 0 { return nil, errors.New("could not find any domains matched by certificate") diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index 4a22b6112..dac0877a5 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -144,7 +144,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { - return certX509.VerifyHostname(domain) == nil + return certX509.VerifyHostname(domain) == nil || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") }) if len(domains) == 0 { return nil, errors.New("could not find any domains matched by certificate") diff --git a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go index 6c6facb26..541d46f2c 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-cdn/jdcloud_cdn.go @@ -215,7 +215,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai // 查询域名配置信息 // REF: https://docs.jdcloud.com/cn/cdn/api/querydomainconfig queryDomainConfigReq := jdcdn.NewQueryDomainConfigRequestWithoutParam() - queryDomainConfigReq.SetDomain(d.config.Domain) + queryDomainConfigReq.SetDomain(domain) queryDomainConfigResp, err := d.sdkClient.QueryDomainConfig(queryDomainConfigReq) d.logger.Debug("sdk request 'cdn.QueryDomainConfig'", slog.Any("request", queryDomainConfigReq), slog.Any("response", queryDomainConfigResp)) if err != nil { @@ -225,7 +225,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai // 设置通讯协议 // REF: https://docs.jdcloud.com/cn/cdn/api/sethttptype setHttpTypeReq := jdcdn.NewSetHttpTypeRequestWithoutParam() - setHttpTypeReq.SetDomain(d.config.Domain) + setHttpTypeReq.SetDomain(domain) setHttpTypeReq.SetHttpType("https") setHttpTypeReq.SetCertFrom("ssl") setHttpTypeReq.SetSslCertId(cloudCertId) diff --git a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go index c97917b21..739948bed 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go +++ b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go @@ -131,7 +131,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { - return certX509.VerifyHostname(domain) == nil + return certX509.VerifyHostname(domain) == nil || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") }) if len(domains) == 0 { return nil, errors.New("could not find any domains matched by certificate") diff --git a/pkg/core/ssl-deployer/providers/rainyun-rcdn/consts.go b/pkg/core/ssl-deployer/providers/rainyun-rcdn/consts.go new file mode 100644 index 000000000..5b3b3eefa --- /dev/null +++ b/pkg/core/ssl-deployer/providers/rainyun-rcdn/consts.go @@ -0,0 +1,6 @@ +package rainyunrcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" +) diff --git a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go index 853b3cf53..4e4a726b9 100644 --- a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go +++ b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go @@ -17,6 +17,9 @@ type SSLDeployerProviderConfig struct { ApiKey string `json:"apiKey"` // RCDN 实例 ID。 InstanceId int64 `json:"instanceId"` + // 域名匹配模式。暂时只支持精确匹配。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } diff --git a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn_test.go b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn_test.go index e9838ed30..07508f2d8 100644 --- a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn_test.go +++ b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - ApiKey: fApiKey, - InstanceId: fInstanceId, - Domain: fDomain, + ApiKey: fApiKey, + InstanceId: fInstanceId, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/upyun-cdn/consts.go b/pkg/core/ssl-deployer/providers/upyun-cdn/consts.go new file mode 100644 index 000000000..6a119706b --- /dev/null +++ b/pkg/core/ssl-deployer/providers/upyun-cdn/consts.go @@ -0,0 +1,10 @@ +package upyuncdn + +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/upyun-cdn/upyun_cdn.go b/pkg/core/ssl-deployer/providers/upyun-cdn/upyun_cdn.go index 9d83508d3..69941c329 100644 --- a/pkg/core/ssl-deployer/providers/upyun-cdn/upyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/upyun-cdn/upyun_cdn.go @@ -5,12 +5,15 @@ import ( "errors" "fmt" "log/slog" + "strings" "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/upyun-ssl" upyunsdk "github.com/certimate-go/certimate/pkg/sdk3rd/upyun/console" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -18,6 +21,9 @@ type SSLDeployerProviderConfig struct { Username string `json:"username"` // 又拍云账号密码。 Password string `json:"password"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -68,10 +74,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -80,44 +82,185 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 获取域名证书配置 - getHttpsServiceManagerResp, err := d.sdkClient.GetHttpsServiceManager(d.config.Domain) - d.logger.Debug("sdk request 'console.GetHttpsServiceManager'", slog.String("request.domain", d.config.Domain), slog.Any("response", getHttpsServiceManagerResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'console.GetHttpsServiceManager': %w", err) + // 获取待部署的域名列表 + 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("could not find any 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("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 判断域名是否已启用 HTTPS。如果已启用,迁移域名证书;否则,设置新证书 - _, lastCertIndex, _ := lo.FindIndexOf(getHttpsServiceManagerResp.Data.Domains, func(item upyunsdk.HttpsServiceManagerDomain) bool { - return item.Https - }) - if lastCertIndex == -1 { - updateHttpsCertificateManagerReq := &upyunsdk.UpdateHttpsCertificateManagerRequest{ - CertificateId: upres.CertId, - Domain: d.config.Domain, - Https: true, - ForceHttps: true, + // 遍历更新域名证书 + 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, upres.CertId); err != nil { + errs = append(errs, err) + } + } } - updateHttpsCertificateManagerResp, err := d.sdkClient.UpdateHttpsCertificateManager(updateHttpsCertificateManagerReq) - d.logger.Debug("sdk request 'console.EnableDomainHttps'", slog.Any("request", updateHttpsCertificateManagerReq), slog.Any("response", updateHttpsCertificateManagerResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'console.UpdateHttpsCertificateManager': %w", err) - } - } else if getHttpsServiceManagerResp.Data.Domains[lastCertIndex].CertificateId != upres.CertId { - migrateHttpsDomainReq := &upyunsdk.MigrateHttpsDomainRequest{ - CertificateId: upres.CertId, - Domain: d.config.Domain, - } - migrateHttpsDomainResp, err := d.sdkClient.MigrateHttpsDomain(migrateHttpsDomainReq) - d.logger.Debug("sdk request 'console.MigrateHttpsDomain'", slog.Any("request", migrateHttpsDomainReq), slog.Any("response", migrateHttpsDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'console.MigrateHttpsDomain': %w", 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) + + // 获取服务列表 + getBucketsPage := 1 + getBucketsPerPage := 10 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + getBucketsReq := &upyunsdk.GetBucketsRequest{ + Type: "ucdn", + Tag: "all", + Status: "all", + IsSecurityCDN: false, + WithDomains: true, + Page: int32(getBucketsPage), + PerPage: int32(getBucketsPerPage), + } + getBucketsResp, err := d.sdkClient.GetBuckets(getBucketsReq) + d.logger.Debug("sdk request 'console.GetBuckets'", slog.Any("request", getBucketsReq), slog.Any("response", getBucketsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'console.GetBuckets': %w", err) + } + + if getBucketsResp.Data == nil { + break + } + + for _, bucketItem := range getBucketsResp.Data.Buckets { + if !bucketItem.Visible { + continue + } + + for _, domainItem := range bucketItem.Domains { + if strings.EqualFold(domainItem.Status, "NORMAL") && !strings.HasSuffix(domainItem.Domain, ".test.upcdn.net") { + domains = append(domains, domainItem.Domain) + } + } + } + + if len(getBucketsResp.Data.Buckets) < getBucketsPerPage { + break + } + + getBucketsPage++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 获取域名证书配置 + getHttpsServiceManagerResp, err := d.sdkClient.GetHttpsServiceManager(domain) + d.logger.Debug("sdk request 'console.GetHttpsServiceManager'", slog.String("request.domain", domain), slog.Any("response", getHttpsServiceManagerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'console.GetHttpsServiceManager': %w", err) + } + + // 判断域名是否已启用 HTTPS + // 如果已启用,迁移域名证书;否则,设置新证书 + _, lastCertIndex, _ := lo.FindIndexOf(getHttpsServiceManagerResp.Data.Domains, func(item upyunsdk.HttpsServiceManagerDomain) bool { + return item.Https + }) + if lastCertIndex == -1 { + updateHttpsCertificateManagerReq := &upyunsdk.UpdateHttpsCertificateManagerRequest{ + CertificateId: cloudCertId, + Domain: domain, + Https: true, + ForceHttps: true, + } + updateHttpsCertificateManagerResp, err := d.sdkClient.UpdateHttpsCertificateManager(updateHttpsCertificateManagerReq) + d.logger.Debug("sdk request 'console.EnableDomainHttps'", slog.Any("request", updateHttpsCertificateManagerReq), slog.Any("response", updateHttpsCertificateManagerResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'console.UpdateHttpsCertificateManager': %w", err) + } + } else if getHttpsServiceManagerResp.Data.Domains[lastCertIndex].CertificateId != cloudCertId { + migrateHttpsDomainReq := &upyunsdk.MigrateHttpsDomainRequest{ + CertificateId: cloudCertId, + Domain: domain, + } + migrateHttpsDomainResp, err := d.sdkClient.MigrateHttpsDomain(migrateHttpsDomainReq) + d.logger.Debug("sdk request 'console.MigrateHttpsDomain'", slog.Any("request", migrateHttpsDomainReq), slog.Any("response", migrateHttpsDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'console.MigrateHttpsDomain': %w", err) + } + } + + return nil +} + func createSDKClient(username, password string) (*upyunsdk.Client, error) { return upyunsdk.NewClient(username, password) } diff --git a/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go b/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go index 7f1b6ade5..768626829 100644 --- a/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go +++ b/pkg/core/ssl-deployer/providers/wangsu-cdn/wangsu_cdn.go @@ -71,10 +71,6 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if len(d.config.Domains) == 0 { - return nil, errors.New("config `domains` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -83,15 +79,31 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + domains := make([]string, 0) + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if len(d.config.Domains) == 0 { + return nil, errors.New("config `domains` is required") + } + + // "*.example.com" → ".example.com",适配网宿云 CDN 要求的泛域名格式 + domains = lo.Map(d.config.Domains, func(domain string, _ int) string { + return strings.TrimPrefix(domain, "*") + }) + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + // 批量修改域名证书配置 // REF: https://www.wangsu.com/document/api-doc/37447 certId, _ := strconv.ParseInt(upres.CertId, 10, 64) batchUpdateCertificateConfigReq := &wangsusdk.BatchUpdateCertificateConfigRequest{ CertificateId: certId, - DomainNames: lo.Map(d.config.Domains, func(domain string, _ int) string { - // "*.example.com" → ".example.com",适配网宿云 CDN 要求的泛域名格式 - return strings.TrimPrefix(domain, "*") - }), + DomainNames: domains, } batchUpdateCertificateConfigResp, err := d.sdkClient.BatchUpdateCertificateConfig(batchUpdateCertificateConfigReq) d.logger.Debug("sdk request 'cdn.BatchUpdateCertificateConfig'", slog.Any("request", batchUpdateCertificateConfigReq), slog.Any("response", batchUpdateCertificateConfigResp)) diff --git a/pkg/sdk3rd/upyun/console/api_get_buckets.go b/pkg/sdk3rd/upyun/console/api_get_buckets.go new file mode 100644 index 000000000..8ddccc80e --- /dev/null +++ b/pkg/sdk3rd/upyun/console/api_get_buckets.go @@ -0,0 +1,85 @@ +package console + +import ( + "context" + "fmt" + "net/http" +) + +type GetBucketsRequest struct { + BucketName string `json:"status"` + BusinessType string `json:"business_type"` + Type string `json:"type"` + Status string `json:"state"` + Tag string `json:"tag"` + IsSecurityCDN bool `json:"security_cdn"` + WithDomains bool `json:"with_domains"` + Page int32 `json:"page"` + PerPage int32 `json:"perPage"` +} + +type GetBucketsResponse struct { + apiResponseBase + Data *struct { + apiResponseBaseData + Buckets []*BucketInfo `json:"buckets"` + Pager BucketPager `json:"pager"` + } `json:"data,omitempty"` +} + +type BucketInfo struct { + BucketName string `json:"bucket_name"` + BusinessType string `json:"business_type"` + Type string `json:"type"` + Status string `json:"status"` + Tag string `json:"tag"` + IsFusionCDN bool `json:"fusion_cdn"` + IsSecurityCDN bool `json:"security_cdn"` + Domains []*BucketDomain `json:"domains"` + Visible bool `json:"visible"` + CreatedAt string `json:"created_at"` +} + +type BucketDomain struct { + Domain string `json:"domain"` + Status string `json:"status"` +} + +type BucketPager struct { + Page int32 `json:"page"` + Pages int64 `json:"pages"` + Total int64 `json:"total"` +} + +func (c *Client) GetBuckets(req *GetBucketsRequest) (*GetBucketsResponse, error) { + return c.GetBucketsWithContext(context.Background(), req) +} + +func (c *Client) GetBucketsWithContext(ctx context.Context, req *GetBucketsRequest) (*GetBucketsResponse, error) { + if err := c.ensureCookieExists(); err != nil { + return nil, err + } + + httpreq, err := c.newRequest(http.MethodGet, "/api/v2/buckets") + if err != nil { + return nil, err + } else { + httpreq.SetQueryParam("bucket_name", req.BucketName) + httpreq.SetQueryParam("business_type", req.BusinessType) + httpreq.SetQueryParam("type", req.Type) + httpreq.SetQueryParam("state", req.Status) + httpreq.SetQueryParam("tag", req.Tag) + httpreq.SetQueryParam("security_cdn", fmt.Sprintf("%v", req.IsSecurityCDN)) + httpreq.SetQueryParam("with_domains", fmt.Sprintf("%v", req.WithDomains)) + httpreq.SetQueryParam("page", fmt.Sprintf("%d", req.Page)) + httpreq.SetQueryParam("perPage", fmt.Sprintf("%d", req.PerPage)) + httpreq.SetContext(ctx) + } + + result := &GetBucketsResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/utils/cert/hostname/hostname.go b/pkg/utils/cert/hostname/hostname.go index 69ddf4bbd..00d76996b 100644 --- a/pkg/utils/cert/hostname/hostname.go +++ b/pkg/utils/cert/hostname/hostname.go @@ -19,14 +19,13 @@ func IsMatch(match, candidate string) bool { return false } - if strings.EqualFold(match, candidate) { - return true - } - mockCert := &x509.Certificate{} if ip := net.ParseIP(match); ip != nil { mockCert.IPAddresses = []net.IP{ip} } else { + if strings.EqualFold(match, candidate) { + return true + } mockCert.DNSNames = []string{match} } return mockCert.VerifyHostname(candidate) == nil diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderRainYunRCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderRainYunRCDN.tsx index 705dd617a..9854a990f 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderRainYunRCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderRainYunRCDN.tsx @@ -1,5 +1,5 @@ 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"; @@ -7,6 +7,8 @@ import { validDomainName } from "@/utils/validators"; import { useFormNestedFieldsContext } from "./_context"; +const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const; + const BizDeployNodeConfigFieldsProviderRainYunRCDN = () => { const { i18n, t } = useTranslation(); @@ -15,8 +17,11 @@ const BizDeployNodeConfigFieldsProviderRainYunRCDN = () => { [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 ( <> { + + ) : ( + void 0 + ) + } + rules={[formRule]} + > + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + { const getInitialValues = (): Nullish>> => { return { instanceId: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -51,12 +79,31 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - instanceId: z.union([z.string(), z.number().int()]).refine((v) => { - return /^\d+$/.test(v + "") && +v > 0; - }, t("workflow_node.deploy.form.rainyun_rcdn_instance_id.placeholder")), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + instanceId: z.union([z.string(), z.number().int()]).refine((v) => { + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.rainyun_rcdn_instance_id.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderRainYunRCDN, { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderUpyunCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderUpyunCDN.tsx index d50745364..0b52439f1 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderUpyunCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderUpyunCDN.tsx @@ -1,13 +1,18 @@ 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 Tips from "@/components/Tips"; 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 BizDeployNodeConfigFieldsProviderUpyunCDN = () => { const { i18n, t } = useTranslation(); @@ -16,8 +21,11 @@ const BizDeployNodeConfigFieldsProviderUpyunCDN = () => { [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 ( <> @@ -25,19 +33,44 @@ const BizDeployNodeConfigFieldsProviderUpyunCDN = () => { + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -45,9 +78,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderUpyunCDN, { diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 6e16cfade..0ab78fee9 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -198,7 +198,7 @@ "workflow_node.deploy.form.shared_domain_match_pattern.option.exact.label": "Exact matches", "workflow_node.deploy.form.shared_domain_match_pattern.option.wildcard.label": "Wildcard matches", "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "via Certificate", - "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "Notes: For wildcard resolution sites, \"Exact matches\" only includes the site itself, does not include its subdomains.", + "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "Notes: For wildcard resolution sites, an exact match of a wildcard domain only includes the site itself, does not include its subdomains.", "workflow_node.deploy.form.1panel_console_auto_restart.label": "Auto restart 1Panel after deployment", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel node name (Optional)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "Please enter 1Panel node name", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 3b2f826e2..4fdd4a2a1 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -197,7 +197,7 @@ "workflow_node.deploy.form.shared_domain_match_pattern.option.exact.label": "精确匹配", "workflow_node.deploy.form.shared_domain_match_pattern.option.wildcard.label": "通配符匹配(泛域名)", "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "根据证书自动匹配", - "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "注意:对于泛解析的站点,泛域名的精确匹配仅包含该泛解析站点本身、不包括相关子域名站点。", + "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "注意:对于泛解析的站点,精确匹配一个泛域名仅包含该站点本身、不包括相关子域名站点。", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启 1Panel 服务", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel 子节点名称(可选)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "请输入 1Panel 子节点名称", From 11da53b05ec4fda8227e94317e49db6d42b7c764 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:03 +0800 Subject: [PATCH 15/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to aliyun live --- .../certdeploy/deployers/sp_aliyun_live.go | 9 +- .../providers/aliyun-clb/aliyun_clb.go | 2 +- .../providers/aliyun-live/aliyun_live.go | 146 +++++++++++++++++- .../providers/aliyun-live/aliyun_live_test.go | 9 +- .../providers/aliyun-live/consts.go | 10 ++ .../providers/aliyun-live/internal/client.go | 74 +++++++++ ...ployNodeConfigFieldsProviderAliyunLive.tsx | 71 +++++++-- .../i18n/locales/en/nls.workflow.nodes.json | 2 +- .../i18n/locales/zh/nls.workflow.nodes.json | 2 +- 9 files changed, 301 insertions(+), 24 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/aliyun-live/consts.go diff --git a/internal/certdeploy/deployers/sp_aliyun_live.go b/internal/certdeploy/deployers/sp_aliyun_live.go index 76a456078..0fd78d88a 100644 --- a/internal/certdeploy/deployers/sp_aliyun_live.go +++ b/internal/certdeploy/deployers/sp_aliyun_live.go @@ -17,10 +17,11 @@ func init() { } provider, err := aliyunlive.NewSSLDeployerProvider(&aliyunlive.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go b/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go index 29d54df4d..84957baae 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-clb/aliyun_clb.go @@ -255,7 +255,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo return fmt.Errorf("failed to execute sdk request 'slb.DescribeDomainExtensions': %w", err) } - // 遍历修改扩展域名 + // 遍历修改扩展域名证书 // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setdomainextensionattribute if describeDomainExtensionsResp.Body.DomainExtensions != nil && describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension != nil { var errs []error diff --git a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go index 27357ee64..2fc6d21ec 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go +++ b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go @@ -12,9 +12,12 @@ import ( alilive "github.com/alibabacloud-go/live-20161101/v2/client" "github.com/alibabacloud-go/tea/dara" "github.com/alibabacloud-go/tea/tea" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-live/internal" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -26,6 +29,9 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 直播流域名(支持泛域名)。 Domain string `json:"domain"` } @@ -68,9 +74,141 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domain` is required") } - // "*.example.com" → ".example.com",适配阿里云 Live 要求的泛域名格式 - domain := strings.TrimPrefix(d.config.Domain, "*") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + // "*.example.com" → ".example.com",适配阿里云 Live 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + domains = []string{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) || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any 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 || + strings.TrimPrefix(d.config.Domain, "*") == strings.TrimPrefix(domain, "*") + }) + if len(domains) == 0 { + return nil, errors.New("could not find any 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 live domains to deploy") + } else { + d.logger.Info("found live 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://help.aliyun.com/zh/live/developer-reference/api-live-2016-11-01-describeliveuserdomains + describeUserLiveDomainsPageNumber := 1 + describeUserLiveDomainsPageSize := 50 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeUserLiveDomainsReq := &alilive.DescribeLiveUserDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + RegionName: tea.String(d.config.Region), + DomainStatus: tea.String("online"), + PageNumber: tea.Int32(int32(describeUserLiveDomainsPageNumber)), + PageSize: tea.Int32(int32(describeUserLiveDomainsPageSize)), + } + describeUserLiveDomainsResp, err := d.sdkClient.DescribeLiveUserDomainsWithContext(ctx, describeUserLiveDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'live.DescribeLiveUserDomains'", slog.Any("request", describeUserLiveDomainsReq), slog.Any("response", describeUserLiveDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'live.DescribeLiveUserDomains': %w", err) + } + + if describeUserLiveDomainsResp.Body == nil || describeUserLiveDomainsResp.Body.Domains == nil { + break + } + + for _, domainItem := range describeUserLiveDomainsResp.Body.Domains.PageData { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(describeUserLiveDomainsResp.Body.Domains.PageData) < describeUserLiveDomainsPageSize { + break + } + + describeUserLiveDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 设置域名证书 // REF: https://help.aliyun.com/zh/live/developer-reference/api-live-2016-11-01-setlivedomaincertificate setLiveDomainSSLCertificateReq := &alilive.SetLiveDomainCertificateRequest{ @@ -84,10 +222,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke setLiveDomainSSLCertificateResp, err := d.sdkClient.SetLiveDomainCertificateWithContext(context.TODO(), setLiveDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'live.SetLiveDomainCertificate'", slog.Any("request", setLiveDomainSSLCertificateReq), slog.Any("response", setLiveDomainSSLCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'live.SetLiveDomainCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'live.SetLiveDomainCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret, region string) (*internal.LiveClient, error) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live_test.go b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live_test.go index 772f21b27..7ae6f3748 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-live/consts.go b/pkg/core/ssl-deployer/providers/aliyun-live/consts.go new file mode 100644 index 000000000..6b4089379 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-live/consts.go @@ -0,0 +1,10 @@ +package aliyunlive + +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/aliyun-live/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-live/internal/client.go index 2f3d39c5b..bbeed402e 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-live/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-live/internal/client.go @@ -35,6 +35,80 @@ func (client *LiveClient) Init(config *openapiutil.Config) (_err error) { return nil } +func (client *LiveClient) DescribeLiveUserDomainsWithContext(ctx context.Context, request *alilive.DescribeLiveUserDomainsRequest, runtime *dara.RuntimeOptions) (_result *alilive.DescribeLiveUserDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.DomainName) { + query["DomainName"] = request.DomainName + } + + if !dara.IsNil(request.DomainSearchType) { + query["DomainSearchType"] = request.DomainSearchType + } + + if !dara.IsNil(request.DomainStatus) { + query["DomainStatus"] = request.DomainStatus + } + + if !dara.IsNil(request.LiveDomainType) { + query["LiveDomainType"] = request.LiveDomainType + } + + if !dara.IsNil(request.OwnerId) { + query["OwnerId"] = request.OwnerId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.RegionName) { + query["RegionName"] = request.RegionName + } + + if !dara.IsNil(request.ResourceGroupId) { + query["ResourceGroupId"] = request.ResourceGroupId + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeLiveUserDomains"), + Version: dara.String("2016-11-01"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alilive.DescribeLiveUserDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *LiveClient) SetLiveDomainCertificateWithContext(ctx context.Context, request *alilive.SetLiveDomainCertificateRequest, runtime *dara.RuntimeOptions) (_result *alilive.SetLiveDomainCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunLive.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunLive.tsx index 4105eb2c0..c91b5bc63 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunLive.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunLive.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 BizDeployNodeConfigFieldsProviderAliyunLive = () => { const { i18n, t } = useTranslation(); @@ -15,8 +20,11 @@ const BizDeployNodeConfigFieldsProviderAliyunLive = () => { [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 ( <> { + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -44,6 +76,7 @@ const BizDeployNodeConfigFieldsProviderAliyunLive = () => { const getInitialValues = (): Nullish>> => { return { region: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -51,10 +84,30 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_live_region.placeholder")), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_live_region.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderAliyunLive, { diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 0ab78fee9..d99b7da90 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -198,7 +198,7 @@ "workflow_node.deploy.form.shared_domain_match_pattern.option.exact.label": "Exact matches", "workflow_node.deploy.form.shared_domain_match_pattern.option.wildcard.label": "Wildcard matches", "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "via Certificate", - "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "Notes: For wildcard resolution sites, an exact match of a wildcard domain only includes the site itself, does not include its subdomains.", + "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "Notes: For the sites which support wildcard resolution, an exact match of a wildcard domain only includes the site itself, does not include its subdomains.", "workflow_node.deploy.form.1panel_console_auto_restart.label": "Auto restart 1Panel after deployment", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel node name (Optional)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "Please enter 1Panel node name", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 4fdd4a2a1..247796288 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -197,7 +197,7 @@ "workflow_node.deploy.form.shared_domain_match_pattern.option.exact.label": "精确匹配", "workflow_node.deploy.form.shared_domain_match_pattern.option.wildcard.label": "通配符匹配(泛域名)", "workflow_node.deploy.form.shared_domain_match_pattern.option.certsan.label": "根据证书自动匹配", - "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "注意:对于泛解析的站点,精确匹配一个泛域名仅包含该站点本身、不包括相关子域名站点。", + "workflow_node.deploy.form.shared_domain_match_pattern.help_wildcard": "注意:对于支持泛解析的站点,精确匹配一个泛域名仅包含该站点本身、不包括相关子域名站点。", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启 1Panel 服务", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel 子节点名称(可选)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "请输入 1Panel 子节点名称", From d6e445f7f8f948450f70ea2762d8c7f3172d6a1b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:04 +0800 Subject: [PATCH 16/30] refactor: clean code --- .../providers/aliyun-alb/aliyun_alb.go | 18 +++++++++--------- .../providers/aliyun-apigw/aliyun_apigw.go | 8 ++++---- .../aliyun-cas-deploy/aliyun_cas_deploy.go | 6 +++--- .../providers/aliyun-cdn/aliyun_cdn.go | 2 +- .../providers/aliyun-dcdn/aliyun_dcdn.go | 2 +- .../providers/aliyun-ddospro/aliyun_ddospro.go | 2 +- .../providers/aliyun-esa/aliyun_esa.go | 2 +- .../providers/aliyun-fc/aliyun_fc.go | 4 ++-- .../providers/aliyun-live/aliyun_live.go | 2 +- .../providers/aliyun-nlb/aliyun_nlb.go | 8 ++++---- .../providers/aliyun-oss/aliyun_oss.go | 2 +- .../providers/aliyun-vod/aliyun_vod.go | 2 +- .../providers/aliyun-waf/aliyun_waf.go | 8 ++++---- .../ssl-deployer/providers/aws-acm/aws_acm.go | 2 +- .../providers/aws-cloudfront/aws_cloudfront.go | 4 ++-- .../providers/azure-keyvault/azure_keyvault.go | 4 ++-- .../providers/gcore-cdn/gcore_cdn.go | 8 ++++---- .../providers/k8s-secret/k8s_secret.go | 6 +++--- pkg/core/ssl-deployer/providers/kong/kong.go | 2 +- .../providers/proxmoxve/proxmoxve.go | 4 ++-- .../providers/qiniu-cdn/qiniu_cdn.go | 8 ++++---- .../providers/qiniu-kodo/qiniu_kodo.go | 2 +- .../providers/qiniu-pili/qiniu_pili.go | 2 +- .../volcengine-imagex/volcengine_imagex.go | 4 ++-- .../providers/volcengine-tos/volcengine_tos.go | 2 +- .../providers/aliyun-cas/aliyun_cas.go | 8 ++++---- .../ssl-manager/providers/aws-acm/aws_acm.go | 6 +++--- .../ssl-manager/providers/aws-iam/aws_iam.go | 6 +++--- .../providers/azure-keyvault/azure_keyvault.go | 4 ++-- .../providers/gcore-cdn/gcore_cdn.go | 2 +- .../providers/qiniu-sslcert/qiniu_sslcert.go | 4 ++-- 31 files changed, 72 insertions(+), 72 deletions(-) diff --git a/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go index be7bc8d36..fb111520e 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-alb/aliyun_alb.go @@ -135,7 +135,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer getLoadBalancerAttributeReq := &alialb.GetLoadBalancerAttributeRequest{ LoadBalancerId: tea.String(d.config.LoadbalancerId), } - getLoadBalancerAttributeResp, err := d.sdkClients.ALB.GetLoadBalancerAttributeWithContext(context.TODO(), getLoadBalancerAttributeReq, &dara.RuntimeOptions{}) + getLoadBalancerAttributeResp, err := d.sdkClients.ALB.GetLoadBalancerAttributeWithContext(ctx, getLoadBalancerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.GetLoadBalancerAttribute'", slog.Any("request", getLoadBalancerAttributeReq), slog.Any("response", getLoadBalancerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.GetLoadBalancerAttribute': %w", err) @@ -158,7 +158,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer LoadBalancerIds: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("HTTPS"), } - listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(context.TODO(), listListenersReq, &dara.RuntimeOptions{}) + listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(ctx, listListenersReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.ListListeners': %w", err) @@ -195,7 +195,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer LoadBalancerIds: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("QUIC"), } - listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(context.TODO(), listListenersReq, &dara.RuntimeOptions{}) + listListenersResp, err := d.sdkClients.ALB.ListListenersWithContext(ctx, listListenersReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.ListListeners': %w", err) @@ -261,7 +261,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo getListenerAttributeReq := &alialb.GetListenerAttributeRequest{ ListenerId: tea.String(cloudListenerId), } - getListenerAttributeResp, err := d.sdkClients.ALB.GetListenerAttributeWithContext(context.TODO(), getListenerAttributeReq, &dara.RuntimeOptions{}) + getListenerAttributeResp, err := d.sdkClients.ALB.GetListenerAttributeWithContext(ctx, getListenerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.GetListenerAttribute'", slog.Any("request", getListenerAttributeReq), slog.Any("response", getListenerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.GetListenerAttribute': %w", err) @@ -278,7 +278,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo CertificateId: tea.String(cloudCertId), }}, } - updateListenerAttributeResp, err := d.sdkClients.ALB.UpdateListenerAttributeWithContext(context.TODO(), updateListenerAttributeReq, &dara.RuntimeOptions{}) + updateListenerAttributeResp, err := d.sdkClients.ALB.UpdateListenerAttributeWithContext(ctx, updateListenerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.UpdateListenerAttribute'", slog.Any("request", updateListenerAttributeReq), slog.Any("response", updateListenerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.UpdateListenerAttribute': %w", err) @@ -303,7 +303,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo ListenerId: tea.String(cloudListenerId), CertificateType: tea.String("Server"), } - listListenerCertificatesResp, err := d.sdkClients.ALB.ListListenerCertificatesWithContext(context.TODO(), listListenerCertificatesReq, &dara.RuntimeOptions{}) + listListenerCertificatesResp, err := d.sdkClients.ALB.ListListenerCertificatesWithContext(ctx, listListenerCertificatesReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.ListListenerCertificates'", slog.Any("request", listListenerCertificatesReq), slog.Any("response", listListenerCertificatesResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.ListListenerCertificates': %w", err) @@ -358,7 +358,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{ CertId: tea.Int64(certificateIdAsInt64), } - getUserCertificateDetailResp, err := d.sdkClients.CAS.GetUserCertificateDetailWithContext(context.TODO(), getUserCertificateDetailReq, &dara.RuntimeOptions{}) + getUserCertificateDetailResp, err := d.sdkClients.CAS.GetUserCertificateDetailWithContext(ctx, getUserCertificateDetailReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) if err != nil { if sdkerr, ok := err.(*tea.SDKError); ok { @@ -401,7 +401,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo }, }, } - associateAdditionalCertificatesFromListenerResp, err := d.sdkClients.ALB.AssociateAdditionalCertificatesWithListenerWithContext(context.TODO(), associateAdditionalCertificatesFromListenerReq, &dara.RuntimeOptions{}) + associateAdditionalCertificatesFromListenerResp, err := d.sdkClients.ALB.AssociateAdditionalCertificatesWithListenerWithContext(ctx, associateAdditionalCertificatesFromListenerReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.AssociateAdditionalCertificatesWithListener'", slog.Any("request", associateAdditionalCertificatesFromListenerReq), slog.Any("response", associateAdditionalCertificatesFromListenerResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.AssociateAdditionalCertificatesWithListener': %w", err) @@ -422,7 +422,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo ListenerId: tea.String(cloudListenerId), Certificates: dissociateAdditionalCertificates, } - dissociateAdditionalCertificatesFromListenerResp, err := d.sdkClients.ALB.DissociateAdditionalCertificatesFromListenerWithContext(context.TODO(), dissociateAdditionalCertificatesFromListenerReq, &dara.RuntimeOptions{}) + dissociateAdditionalCertificatesFromListenerResp, err := d.sdkClients.ALB.DissociateAdditionalCertificatesFromListenerWithContext(ctx, dissociateAdditionalCertificatesFromListenerReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'alb.DissociateAdditionalCertificatesFromListener'", slog.Any("request", dissociateAdditionalCertificatesFromListenerReq), slog.Any("response", dissociateAdditionalCertificatesFromListenerResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'alb.DissociateAdditionalCertificatesFromListener': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go index 1415953f7..97e181890 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -129,7 +129,7 @@ func (d *SSLDeployerProvider) deployToTraditional(ctx context.Context, certPEM s CertificateBody: tea.String(certPEM), CertificatePrivateKey: tea.String(privkeyPEM), } - setDomainCertificateResp, err := d.sdkClients.TraditionalAPIGateway.SetDomainCertificateWithContext(context.TODO(), setDomainCertificateReq, &dara.RuntimeOptions{}) + setDomainCertificateResp, err := d.sdkClients.TraditionalAPIGateway.SetDomainCertificateWithContext(ctx, setDomainCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'apigateway.SetDomainCertificate'", slog.Any("request", setDomainCertificateReq), slog.Any("response", setDomainCertificateResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'apigateway.SetDomainCertificate': %w", err) @@ -155,7 +155,7 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s // 查询域名 // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain getDomainReq := &aliapig.GetDomainRequest{} - getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomainWithContext(context.TODO(), tea.String(domainId), getDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomainWithContext(ctx, tea.String(domainId), getDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'apig.GetDomain'", slog.String("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'apig.GetDomain': %w", err) @@ -181,7 +181,7 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s TlsCipherSuitesConfig: getDomainResp.Body.Data.TlsCipherSuitesConfig, CertIdentifier: tea.String(upres.ExtendedData["CertIdentifier"].(string)), } - updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomainWithContext(context.TODO(), tea.String(domainId), updateDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomainWithContext(ctx, tea.String(domainId), updateDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'apig.UpdateDomain'", slog.String("domainId", domainId), slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'apig.UpdateDomain': %w", err) @@ -209,7 +209,7 @@ func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Contex PageNumber: tea.Int32(int32(listDomainsPageNumber)), PageSize: tea.Int32(int32(listDomainsPageSize)), } - listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomainsWithContext(context.TODO(), listDomainsReq, make(map[string]*string), &dara.RuntimeOptions{}) + listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomainsWithContext(ctx, listDomainsReq, make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) if err != nil { return "", fmt.Errorf("failed to execute sdk request 'apig.ListDomains': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go b/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go index 1967824b0..eeb148f0b 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go @@ -102,7 +102,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke ShowSize: tea.Int32(1), CurrentPage: tea.Int32(1), } - listContactResp, err := d.sdkClient.ListContactWithContext(context.TODO(), listContactReq, &dara.RuntimeOptions{}) + listContactResp, err := d.sdkClient.ListContactWithContext(ctx, listContactReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cas.ListContact'", slog.Any("request", listContactReq), slog.Any("response", listContactResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.ListContact': %w", err) @@ -122,7 +122,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke ResourceIds: tea.String(strings.Join(d.config.ResourceIds, ",")), ContactIds: tea.String(strings.Join(contactIds, ",")), } - createDeploymentJobResp, err := d.sdkClient.CreateDeploymentJobWithContext(context.TODO(), createDeploymentJobReq, &dara.RuntimeOptions{}) + createDeploymentJobResp, err := d.sdkClient.CreateDeploymentJobWithContext(ctx, createDeploymentJobReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cas.CreateDeploymentJob'", slog.Any("request", createDeploymentJobReq), slog.Any("response", createDeploymentJobResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.CreateDeploymentJob': %w", err) @@ -140,7 +140,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke describeDeploymentJobReq := &alicas.DescribeDeploymentJobRequest{ JobId: createDeploymentJobResp.Body.JobId, } - describeDeploymentJobResp, err := d.sdkClient.DescribeDeploymentJobWithContext(context.TODO(), describeDeploymentJobReq, &dara.RuntimeOptions{}) + describeDeploymentJobResp, err := d.sdkClient.DescribeDeploymentJobWithContext(ctx, describeDeploymentJobReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cas.DescribeDeploymentJob'", slog.Any("request", describeDeploymentJobReq), slog.Any("response", describeDeploymentJobResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.DescribeDeploymentJob': %w", 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 decda89be..94aa8b820 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -243,7 +243,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai Else(tea.String("ap-southeast-1")), SSLProtocol: tea.String("on"), } - setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificateWithContext(context.TODO(), setCdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) + setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificateWithContext(ctx, setCdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'cdn.SetCdnDomainSSLCertificate'", slog.Any("request", setCdnDomainSSLCertificateReq), slog.Any("response", setCdnDomainSSLCertificateResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index dac0877a5..bd6eeb80b 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -244,7 +244,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai Else(tea.String("ap-southeast-1")), SSLProtocol: tea.String("on"), } - setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificateWithContext(context.TODO(), setDcdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) + setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificateWithContext(ctx, setDcdnDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'dcdn.SetDcdnDomainSSLCertificate'", slog.Any("request", setDcdnDomainSSLCertificateReq), slog.Any("response", setDcdnDomainSSLCertificateResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go index 4490da2cb..b69cabe5c 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go @@ -99,7 +99,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke Domain: tea.String(d.config.Domain), CertIdentifier: tea.String(upres.ExtendedData["CertIdentifier"].(string)), } - associateWebCertResp, err := d.sdkClient.AssociateWebCertWithContext(context.TODO(), associateWebCertReq, &dara.RuntimeOptions{}) + associateWebCertResp, err := d.sdkClient.AssociateWebCertWithContext(ctx, associateWebCertReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'dcdn.AssociateWebCert'", slog.Any("request", associateWebCertReq), slog.Any("response", associateWebCertResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'dcdn.AssociateWebCert': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-esa/aliyun_esa.go b/pkg/core/ssl-deployer/providers/aliyun-esa/aliyun_esa.go index ebcede0e6..fdf849eb9 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-esa/aliyun_esa.go +++ b/pkg/core/ssl-deployer/providers/aliyun-esa/aliyun_esa.go @@ -103,7 +103,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke CasId: tea.Int64(certId), Region: tea.String(d.config.Region), } - setCertificateResp, err := d.sdkClient.SetCertificateWithContext(context.TODO(), setCertificateReq, &dara.RuntimeOptions{}) + setCertificateResp, err := d.sdkClient.SetCertificateWithContext(ctx, setCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'esa.SetCertificate'", slog.Any("request", setCertificateReq), slog.Any("response", setCertificateResp)) if err != nil { var sdkError *tea.SDKError diff --git a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go index 3359a7aef..aca2872cc 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go +++ b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go @@ -97,7 +97,7 @@ func (d *SSLDeployerProvider) deployToFC3(ctx context.Context, certPEM string, p // 获取自定义域名 // REF: https://help.aliyun.com/zh/functioncompute/fc-3-0/developer-reference/api-fc-2023-03-30-getcustomdomain - getCustomDomainResp, err := d.sdkClients.FC3.GetCustomDomainWithContext(context.TODO(), tea.String(d.config.Domain), make(map[string]*string), &dara.RuntimeOptions{}) + getCustomDomainResp, err := d.sdkClients.FC3.GetCustomDomainWithContext(ctx, tea.String(d.config.Domain), make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'fc.GetCustomDomain'", slog.Any("response", getCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.GetCustomDomain': %w", err) @@ -119,7 +119,7 @@ func (d *SSLDeployerProvider) deployToFC3(ctx context.Context, certPEM string, p if tea.StringValue(updateCustomDomainReq.Body.Protocol) == "HTTP" { updateCustomDomainReq.Body.Protocol = tea.String("HTTP,HTTPS") } - updateCustomDomainResp, err := d.sdkClients.FC3.UpdateCustomDomainWithContext(context.TODO(), tea.String(d.config.Domain), updateCustomDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + updateCustomDomainResp, err := d.sdkClients.FC3.UpdateCustomDomainWithContext(ctx, tea.String(d.config.Domain), updateCustomDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'fc.UpdateCustomDomain'", slog.Any("request", updateCustomDomainReq), slog.Any("response", updateCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.UpdateCustomDomain': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go index 2fc6d21ec..4386e752f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go +++ b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go @@ -219,7 +219,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai SSLPub: tea.String(certPEM), SSLPri: tea.String(privkeyPEM), } - setLiveDomainSSLCertificateResp, err := d.sdkClient.SetLiveDomainCertificateWithContext(context.TODO(), setLiveDomainSSLCertificateReq, &dara.RuntimeOptions{}) + setLiveDomainSSLCertificateResp, err := d.sdkClient.SetLiveDomainCertificateWithContext(ctx, setLiveDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'live.SetLiveDomainCertificate'", slog.Any("request", setLiveDomainSSLCertificateReq), slog.Any("response", setLiveDomainSSLCertificateResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'live.SetLiveDomainCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go b/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go index 55ff9625e..33ef757db 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/pkg/core/ssl-deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -124,7 +124,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer getLoadBalancerAttributeReq := &alinlb.GetLoadBalancerAttributeRequest{ LoadBalancerId: tea.String(d.config.LoadbalancerId), } - getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttributeWithContext(context.TODO(), getLoadBalancerAttributeReq, &dara.RuntimeOptions{}) + getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttributeWithContext(ctx, getLoadBalancerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'nlb.GetLoadBalancerAttribute'", slog.Any("request", getLoadBalancerAttributeReq), slog.Any("response", getLoadBalancerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'nlb.GetLoadBalancerAttribute': %w", err) @@ -147,7 +147,7 @@ func (d *SSLDeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCer LoadBalancerIds: tea.StringSlice([]string{d.config.LoadbalancerId}), ListenerProtocol: tea.String("TCPSSL"), } - listListenersResp, err := d.sdkClient.ListListenersWithContext(context.TODO(), listListenersReq, &dara.RuntimeOptions{}) + listListenersResp, err := d.sdkClient.ListListenersWithContext(ctx, listListenersReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'nlb.ListListeners'", slog.Any("request", listListenersReq), slog.Any("response", listListenersResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'nlb.ListListeners': %w", err) @@ -213,7 +213,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo getListenerAttributeReq := &alinlb.GetListenerAttributeRequest{ ListenerId: tea.String(cloudListenerId), } - getListenerAttributeResp, err := d.sdkClient.GetListenerAttributeWithContext(context.TODO(), getListenerAttributeReq, &dara.RuntimeOptions{}) + getListenerAttributeResp, err := d.sdkClient.GetListenerAttributeWithContext(ctx, getListenerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'nlb.GetListenerAttribute'", slog.Any("request", getListenerAttributeReq), slog.Any("response", getListenerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'nlb.GetListenerAttribute': %w", err) @@ -225,7 +225,7 @@ func (d *SSLDeployerProvider) updateListenerCertificate(ctx context.Context, clo ListenerId: tea.String(cloudListenerId), CertificateIds: []*string{tea.String(cloudCertId)}, } - updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttributeWithContext(context.TODO(), updateListenerAttributeReq, &dara.RuntimeOptions{}) + updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttributeWithContext(ctx, updateListenerAttributeReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'nlb.UpdateListenerAttribute'", slog.Any("request", updateListenerAttributeReq), slog.Any("response", updateListenerAttributeResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'nlb.UpdateListenerAttribute': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-oss/aliyun_oss.go b/pkg/core/ssl-deployer/providers/aliyun-oss/aliyun_oss.go index 152f16867..4ca28bc34 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-oss/aliyun_oss.go +++ b/pkg/core/ssl-deployer/providers/aliyun-oss/aliyun_oss.go @@ -82,7 +82,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke }, }, } - putCnameResp, err := d.sdkClient.PutCname(context.TODO(), putCnameReq) + putCnameResp, err := d.sdkClient.PutCname(ctx, putCnameReq) d.logger.Debug("sdk request 'oss.PutCname'", slog.Any("request", putCnameReq), slog.Any("response", putCnameResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'oss.PutCname': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go index ec5c3a595..ea99b8012 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go @@ -105,7 +105,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke Else(tea.String("ap-southeast-1")), SSLProtocol: tea.String("on"), } - setVodDomainSSLCertificateResp, err := d.sdkClient.SetVodDomainSSLCertificateWithContext(context.TODO(), setVodDomainSSLCertificateReq, &dara.RuntimeOptions{}) + setVodDomainSSLCertificateResp, err := d.sdkClient.SetVodDomainSSLCertificateWithContext(ctx, setVodDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'live.SetVodDomainSSLCertificate'", slog.Any("request", setVodDomainSSLCertificateReq), slog.Any("response", setVodDomainSSLCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'live.SetVodDomainSSLCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go b/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go index 3d88c15eb..6fb4ecb99 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go +++ b/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go @@ -121,7 +121,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, InstanceId: tea.String(d.config.InstanceId), RegionId: tea.String(d.config.Region), } - describeDefaultHttpsResp, err := d.sdkClient.DescribeDefaultHttpsWithContext(context.TODO(), describeDefaultHttpsReq, &dara.RuntimeOptions{}) + describeDefaultHttpsResp, err := d.sdkClient.DescribeDefaultHttpsWithContext(ctx, describeDefaultHttpsReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.DescribeDefaultHttps'", slog.Any("request", describeDefaultHttpsReq), slog.Any("response", describeDefaultHttpsResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'waf.DescribeDefaultHttps': %w", err) @@ -145,7 +145,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, modifyDefaultHttpsReq.EnableTLSv3 = describeDefaultHttpsResp.Body.DefaultHttps.EnableTLSv3 } } - modifyDefaultHttpsResp, err := d.sdkClient.ModifyDefaultHttpsWithContext(context.TODO(), modifyDefaultHttpsReq, &dara.RuntimeOptions{}) + modifyDefaultHttpsResp, err := d.sdkClient.ModifyDefaultHttpsWithContext(ctx, modifyDefaultHttpsReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.ModifyDefaultHttps'", slog.Any("request", modifyDefaultHttpsReq), slog.Any("response", modifyDefaultHttpsResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'waf.ModifyDefaultHttps': %w", err) @@ -160,7 +160,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, RegionId: tea.String(d.config.Region), Domain: tea.String(d.config.Domain), } - describeDomainDetailResp, err := d.sdkClient.DescribeDomainDetailWithContext(context.TODO(), describeDomainDetailReq, &dara.RuntimeOptions{}) + describeDomainDetailResp, err := d.sdkClient.DescribeDomainDetailWithContext(ctx, describeDomainDetailReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.DescribeDomainDetail'", slog.Any("request", describeDomainDetailReq), slog.Any("response", describeDomainDetailResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'waf.DescribeDomainDetail': %w", err) @@ -176,7 +176,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, Redirect: &aliwaf.ModifyDomainRequestRedirect{Loadbalance: tea.String("iphash")}, } modifyDomainReq = assign(modifyDomainReq, describeDomainDetailResp.Body) - modifyDomainResp, err := d.sdkClient.ModifyDomainWithContext(context.TODO(), modifyDomainReq, &dara.RuntimeOptions{}) + modifyDomainResp, err := d.sdkClient.ModifyDomainWithContext(ctx, modifyDomainReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.ModifyDomain'", slog.Any("request", modifyDomainReq), slog.Any("response", modifyDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'waf.ModifyDomain': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aws-acm/aws_acm.go b/pkg/core/ssl-deployer/providers/aws-acm/aws_acm.go index 30d627dbe..b71b7821a 100644 --- a/pkg/core/ssl-deployer/providers/aws-acm/aws_acm.go +++ b/pkg/core/ssl-deployer/providers/aws-acm/aws_acm.go @@ -98,7 +98,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke CertificateChain: ([]byte)(intermediaCertPEM), PrivateKey: ([]byte)(privkeyPEM), } - importCertificateResp, err := d.sdkClient.ImportCertificate(context.TODO(), importCertificateReq) + importCertificateResp, err := d.sdkClient.ImportCertificate(ctx, importCertificateReq) d.logger.Debug("sdk request 'acm.ImportCertificate'", slog.Any("request", importCertificateReq), slog.Any("response", importCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'acm.ImportCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aws-cloudfront/aws_cloudfront.go b/pkg/core/ssl-deployer/providers/aws-cloudfront/aws_cloudfront.go index 79f4fe50d..cbaa9a0a2 100644 --- a/pkg/core/ssl-deployer/providers/aws-cloudfront/aws_cloudfront.go +++ b/pkg/core/ssl-deployer/providers/aws-cloudfront/aws_cloudfront.go @@ -113,7 +113,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke getDistributionConfigReq := &cloudfront.GetDistributionConfigInput{ Id: aws.String(d.config.DistributionId), } - getDistributionConfigResp, err := d.sdkClient.GetDistributionConfig(context.TODO(), getDistributionConfigReq) + getDistributionConfigResp, err := d.sdkClient.GetDistributionConfig(ctx, getDistributionConfigReq) d.logger.Debug("sdk request 'cloudfront.GetDistributionConfig'", slog.Any("request", getDistributionConfigReq), slog.Any("response", getDistributionConfigResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cloudfront.GetDistributionConfig': %w", err) @@ -145,7 +145,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke updateDistributionReq.DistributionConfig.ViewerCertificate.SSLSupportMethod = types.SSLSupportMethodSniOnly } } - updateDistributionResp, err := d.sdkClient.UpdateDistribution(context.TODO(), updateDistributionReq) + updateDistributionResp, err := d.sdkClient.UpdateDistribution(ctx, updateDistributionReq) d.logger.Debug("sdk request 'cloudfront.UpdateDistribution'", slog.Any("request", updateDistributionReq), slog.Any("response", updateDistributionResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cloudfront.UpdateDistribution': %w", err) diff --git a/pkg/core/ssl-deployer/providers/azure-keyvault/azure_keyvault.go b/pkg/core/ssl-deployer/providers/azure-keyvault/azure_keyvault.go index bd113189e..1261f8cd0 100644 --- a/pkg/core/ssl-deployer/providers/azure-keyvault/azure_keyvault.go +++ b/pkg/core/ssl-deployer/providers/azure-keyvault/azure_keyvault.go @@ -106,7 +106,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } else { // 获取证书 // REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificate/get-certificate - getCertificateResp, err := d.sdkClient.GetCertificate(context.TODO(), d.config.CertificateName, "", nil) + getCertificateResp, err := d.sdkClient.GetCertificate(ctx, d.config.CertificateName, "", nil) d.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", d.config.CertificateName), slog.Any("response", getCertificateResp)) if err != nil { var respErr *azcore.ResponseError @@ -134,7 +134,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke "certimate/cert-sn": to.Ptr(certX509.SerialNumber.Text(16)), }, } - importCertificateResp, err := d.sdkClient.ImportCertificate(context.TODO(), d.config.CertificateName, importCertificateParams, nil) + importCertificateResp, err := d.sdkClient.ImportCertificate(ctx, d.config.CertificateName, importCertificateParams, nil) d.logger.Debug("sdk request 'keyvault.ImportCertificate'", slog.String("request.certificateName", d.config.CertificateName), slog.Any("request.parameters", importCertificateParams), slog.Any("response", importCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'keyvault.ImportCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/gcore-cdn/gcore_cdn.go b/pkg/core/ssl-deployer/providers/gcore-cdn/gcore_cdn.go index 92735e23c..cd43c755b 100644 --- a/pkg/core/ssl-deployer/providers/gcore-cdn/gcore_cdn.go +++ b/pkg/core/ssl-deployer/providers/gcore-cdn/gcore_cdn.go @@ -96,7 +96,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } else { // 获取证书 // REF: https://api.gcore.com/docs/cdn#tag/SSL-certificates/paths/~1cdn~1sslData~1%7Bssl_id%7D/get - getCertificateDetailResp, err := d.sdkClients.SSLCerts.Get(context.TODO(), d.config.CertificateId) + getCertificateDetailResp, err := d.sdkClients.SSLCerts.Get(ctx, d.config.CertificateId) d.logger.Debug("sdk request 'sslcerts.Get'", slog.Int64("sslId", d.config.CertificateId), slog.Any("response", getCertificateDetailResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Get': %w", err) @@ -110,7 +110,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke PrivateKey: privkeyPEM, ValidateRootCA: false, } - changeCertificateResp, err := d.sdkClients.SSLCerts.Update(context.TODO(), getCertificateDetailResp.ID, changeCertificateReq) + changeCertificateResp, err := d.sdkClients.SSLCerts.Update(ctx, getCertificateDetailResp.ID, changeCertificateReq) d.logger.Debug("sdk request 'sslcerts.Update'", slog.Int64("sslId", getCertificateDetailResp.ID), slog.Any("request", changeCertificateReq), slog.Any("response", changeCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Update': %w", err) @@ -121,7 +121,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 获取 CDN 资源详情 // REF: https://api.gcore.com/docs/cdn#tag/CDN-resources/paths/~1cdn~1resources~1%7Bresource_id%7D/get - getResourceResp, err := d.sdkClients.Resources.Get(context.TODO(), d.config.ResourceId) + getResourceResp, err := d.sdkClients.Resources.Get(ctx, d.config.ResourceId) d.logger.Debug("sdk request 'resources.Get'", slog.Any("resourceId", d.config.ResourceId), slog.Any("response", getResourceResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'resources.Get': %w", err) @@ -146,7 +146,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if getResourceResp.ProxySSLData != 0 { updateResourceReq.ProxySSLData = &getResourceResp.ProxySSLData } - updateResourceResp, err := d.sdkClients.Resources.Update(context.TODO(), d.config.ResourceId, updateResourceReq) + updateResourceResp, err := d.sdkClients.Resources.Update(ctx, d.config.ResourceId, updateResourceReq) d.logger.Debug("sdk request 'resources.Update'", slog.Int64("resourceId", d.config.ResourceId), slog.Any("request", updateResourceReq), slog.Any("response", updateResourceResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'resources.Update': %w", err) diff --git a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go index 0387fd8f3..1a74602a2 100644 --- a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go +++ b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go @@ -112,7 +112,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } // 获取 Secret 实例,如果不存在则创建 - secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(context.TODO(), d.config.SecretName, k8smeta.GetOptions{}) + secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(ctx, d.config.SecretName, k8smeta.GetOptions{}) if err != nil { if !k8serrors.IsNotFound(err) { return nil, fmt.Errorf("failed to get kubernetes secret: %w", err) @@ -134,7 +134,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke secretPayload.Data[d.config.SecretDataKeyForCrt] = []byte(certPEM) secretPayload.Data[d.config.SecretDataKeyForKey] = []byte(privkeyPEM) - secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Create(context.TODO(), secretPayload, k8smeta.CreateOptions{}) + secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Create(ctx, secretPayload, k8smeta.CreateOptions{}) d.logger.Debug("kubernetes operate 'Secrets.Create'", slog.String("namespace", d.config.Namespace), slog.Any("secret", secretPayload)) if err != nil { return nil, fmt.Errorf("failed to create kubernetes secret: %w", err) @@ -164,7 +164,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } secretPayload.Data[d.config.SecretDataKeyForCrt] = []byte(certPEM) secretPayload.Data[d.config.SecretDataKeyForKey] = []byte(privkeyPEM) - secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Update(context.TODO(), secretPayload, k8smeta.UpdateOptions{}) + secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Update(ctx, secretPayload, k8smeta.UpdateOptions{}) d.logger.Debug("kubernetes operate 'Secrets.Update'", slog.String("namespace", d.config.Namespace), slog.Any("secret", secretPayload)) if err != nil { return nil, fmt.Errorf("failed to update kubernetes secret: %w", err) diff --git a/pkg/core/ssl-deployer/providers/kong/kong.go b/pkg/core/ssl-deployer/providers/kong/kong.go index 77511f980..8f659e27b 100644 --- a/pkg/core/ssl-deployer/providers/kong/kong.go +++ b/pkg/core/ssl-deployer/providers/kong/kong.go @@ -100,7 +100,7 @@ func (d *SSLDeployerProvider) deployToCertificate(ctx context.Context, certPEM s Key: kong.String(privkeyPEM), SNIs: kong.StringSlice(certX509.DNSNames...), } - updateCertificateResp, err := d.sdkClient.Certificates.Update(context.TODO(), updateCertificateReq) + updateCertificateResp, err := d.sdkClient.Certificates.Update(ctx, updateCertificateReq) d.logger.Debug("sdk request 'kong.UpdateCertificate'", slog.String("sslId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'kong.UpdateCertificate': %w", err) diff --git a/pkg/core/ssl-deployer/providers/proxmoxve/proxmoxve.go b/pkg/core/ssl-deployer/providers/proxmoxve/proxmoxve.go index 37a02a5de..2fc751c47 100644 --- a/pkg/core/ssl-deployer/providers/proxmoxve/proxmoxve.go +++ b/pkg/core/ssl-deployer/providers/proxmoxve/proxmoxve.go @@ -71,14 +71,14 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 获取节点信息 // REF: https://pve.proxmox.com/pve-docs/api-viewer/index.html#/nodes/{node} - node, err := d.sdkClient.Node(context.TODO(), d.config.NodeName) + node, err := d.sdkClient.Node(ctx, d.config.NodeName) if err != nil { return nil, fmt.Errorf("failed to get node '%s': %w", d.config.NodeName, err) } // 上传自定义证书 // REF: https://pve.proxmox.com/pve-docs/api-viewer/index.html#/nodes/{node}/certificates/custom - err = node.UploadCustomCertificate(context.TODO(), &proxmox.CustomCertificate{ + err = node.UploadCustomCertificate(ctx, &proxmox.CustomCertificate{ Certificates: certPEM, Key: privkeyPEM, Force: true, diff --git a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go index 739948bed..b2d60e9f7 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go +++ b/pkg/core/ssl-deployer/providers/qiniu-cdn/qiniu_cdn.go @@ -182,7 +182,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro default: } - getDomainListResp, err := d.sdkClient.GetDomainList(context.TODO(), getDomainListMarker, 100) + getDomainListResp, err := d.sdkClient.GetDomainList(ctx, getDomainListMarker, 100) d.logger.Debug("sdk request 'cdn.GetDomainList'", slog.String("request.marker", getDomainListMarker), slog.Any("response", getDomainListResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetDomainList': %w", err) @@ -210,7 +210,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 获取域名信息 // REF: https://developer.qiniu.com/fusion/4246/the-domain-name - getDomainInfoResp, err := d.sdkClient.GetDomainInfo(context.TODO(), domain) + getDomainInfoResp, err := d.sdkClient.GetDomainInfo(ctx, domain) d.logger.Debug("sdk request 'cdn.GetDomainInfo'", slog.String("request.domain", domain), slog.Any("response", getDomainInfoResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'cdn.GetDomainInfo': %w", err) @@ -220,13 +220,13 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai // 如果已启用,修改域名证书;否则,启用 HTTPS // REF: https://developer.qiniu.com/fusion/4246/the-domain-name if getDomainInfoResp.Https == nil || getDomainInfoResp.Https.CertID == "" { - enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(context.TODO(), domain, cloudCertId, true, true) + enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(ctx, domain, cloudCertId, true, true) d.logger.Debug("sdk request 'cdn.EnableDomainHttps'", slog.String("request.domain", domain), slog.String("request.certId", cloudCertId), slog.Any("response", enableDomainHttpsResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'cdn.EnableDomainHttps': %w", err) } } else if getDomainInfoResp.Https.CertID != cloudCertId { - modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(context.TODO(), domain, cloudCertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) + modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(ctx, domain, cloudCertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("request.domain", domain), slog.String("request.certId", cloudCertId), slog.Any("response", modifyDomainHttpsConfResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'cdn.ModifyDomainHttpsConf': %w", err) diff --git a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go index 7dcec1724..66dd56019 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go +++ b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go @@ -78,7 +78,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } // 绑定空间域名证书 - bindBucketCertResp, err := d.sdkClient.BindBucketCert(context.TODO(), d.config.Domain, upres.CertId) + bindBucketCertResp, err := d.sdkClient.BindBucketCert(ctx, d.config.Domain, upres.CertId) d.logger.Debug("sdk request 'kodo.BindCert'", slog.String("request.domain", d.config.Domain), slog.String("request.certId", upres.CertId), slog.Any("response", bindBucketCertResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'kodo.BindCert': %w", err) diff --git a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go index e4e99c4d6..bbe2fced1 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go +++ b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go @@ -85,7 +85,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke Domain: d.config.Domain, CertName: upres.CertName, } - err = d.sdkClient.SetDomainCert(context.TODO(), setDomainCertReq) + err = d.sdkClient.SetDomainCert(ctx, setDomainCertReq) d.logger.Debug("sdk request 'pili.SetDomainCert'", slog.Any("request", setDomainCertReq)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'pili.SetDomainCert': %w", err) diff --git a/pkg/core/ssl-deployer/providers/volcengine-imagex/volcengine_imagex.go b/pkg/core/ssl-deployer/providers/volcengine-imagex/volcengine_imagex.go index a129b33bf..f84081e42 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-imagex/volcengine_imagex.go +++ b/pkg/core/ssl-deployer/providers/volcengine-imagex/volcengine_imagex.go @@ -94,7 +94,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke ServiceID: d.config.ServiceId, DomainName: d.config.Domain, } - getDomainConfigResp, err := d.sdkClient.GetDomainConfig(context.TODO(), getDomainConfigReq) + getDomainConfigResp, err := d.sdkClient.GetDomainConfig(ctx, getDomainConfigReq) d.logger.Debug("sdk request 'imagex.GetDomainConfig'", slog.Any("request", getDomainConfigReq), slog.Any("response", getDomainConfigResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'imagex.GetDomainConfig': %w", err) @@ -123,7 +123,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke updateHttpsReq.UpdateHTTPSBody.HTTPS.ForceRedirectType = getDomainConfigResp.Result.HTTPSConfig.ForceRedirectType updateHttpsReq.UpdateHTTPSBody.HTTPS.ForceRedirectCode = getDomainConfigResp.Result.HTTPSConfig.ForceRedirectCode } - updateHttpsResp, err := d.sdkClient.UpdateHTTPS(context.TODO(), updateHttpsReq) + updateHttpsResp, err := d.sdkClient.UpdateHTTPS(ctx, updateHttpsReq) d.logger.Debug("sdk request 'imagex.UpdateHttps'", slog.Any("request", updateHttpsReq), slog.Any("response", updateHttpsResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'imagex.UpdateHttps': %w", err) diff --git a/pkg/core/ssl-deployer/providers/volcengine-tos/volcengine_tos.go b/pkg/core/ssl-deployer/providers/volcengine-tos/volcengine_tos.go index 6ff808540..38c31ddbb 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-tos/volcengine_tos.go +++ b/pkg/core/ssl-deployer/providers/volcengine-tos/volcengine_tos.go @@ -96,7 +96,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke CertID: upres.CertId, }, } - putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomain(context.TODO(), putBucketCustomDomainReq) + putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomain(ctx, putBucketCustomDomainReq) d.logger.Debug("sdk request 'tos.PutBucketCustomDomain'", slog.Any("request", putBucketCustomDomainReq), slog.Any("response", putBucketCustomDomainResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'tos.PutBucketCustomDomain': %w", err) diff --git a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go index f139920d7..178711738 100644 --- a/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go +++ b/pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go @@ -88,7 +88,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey ShowSize: tea.Int64(int64(listUserCertificateOrderLimit)), OrderType: tea.String("CERT"), } - listUserCertificateOrderResp, err := m.sdkClient.ListUserCertificateOrderWithContext(context.TODO(), listUserCertificateOrderReq, &dara.RuntimeOptions{}) + listUserCertificateOrderResp, err := m.sdkClient.ListUserCertificateOrderWithContext(ctx, listUserCertificateOrderReq, &dara.RuntimeOptions{}) m.logger.Debug("sdk request 'cas.ListUserCertificateOrder'", slog.Any("request", listUserCertificateOrderReq), slog.Any("response", listUserCertificateOrderResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.ListUserCertificateOrder': %w", err) @@ -116,7 +116,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{ CertId: certItem.CertificateId, } - getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(context.TODO(), getUserCertificateDetailReq, &dara.RuntimeOptions{}) + getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(ctx, getUserCertificateDetailReq, &dara.RuntimeOptions{}) m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err) @@ -156,7 +156,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey Cert: tea.String(certPEM), Key: tea.String(privkeyPEM), } - uploadUserCertificateResp, err := m.sdkClient.UploadUserCertificateWithContext(context.TODO(), uploadUserCertificateReq, &dara.RuntimeOptions{}) + uploadUserCertificateResp, err := m.sdkClient.UploadUserCertificateWithContext(ctx, uploadUserCertificateReq, &dara.RuntimeOptions{}) m.logger.Debug("sdk request 'cas.UploadUserCertificate'", slog.Any("request", uploadUserCertificateReq), slog.Any("response", uploadUserCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.UploadUserCertificate': %w", err) @@ -168,7 +168,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey CertId: uploadUserCertificateResp.Body.CertId, CertFilter: tea.Bool(true), } - getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(context.TODO(), getUserCertificateDetailReq, &dara.RuntimeOptions{}) + getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetailWithContext(ctx, getUserCertificateDetailReq, &dara.RuntimeOptions{}) m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err) diff --git a/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go b/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go index e0bfe8b6a..0e3dbe2c5 100644 --- a/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go +++ b/pkg/core/ssl-manager/providers/aws-acm/aws_acm.go @@ -86,7 +86,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey NextToken: listCertificatesNextToken, MaxItems: aws.Int32(1000), } - listCertificatesResp, err := m.sdkClient.ListCertificates(context.TODO(), listCertificatesReq) + listCertificatesResp, err := m.sdkClient.ListCertificates(ctx, listCertificatesReq) m.logger.Debug("sdk request 'acm.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'acm.ListCertificates': %w", err) @@ -110,7 +110,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey getCertificateReq := &awsacm.GetCertificateInput{ CertificateArn: certItem.CertificateArn, } - getCertificateResp, err := m.sdkClient.GetCertificate(context.TODO(), getCertificateReq) + getCertificateResp, err := m.sdkClient.GetCertificate(ctx, getCertificateReq) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'acm.GetCertificate': %w", err) } else { @@ -140,7 +140,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey CertificateChain: ([]byte)(intermediaCertPEM), PrivateKey: ([]byte)(privkeyPEM), } - importCertificateResp, err := m.sdkClient.ImportCertificate(context.TODO(), importCertificateReq) + importCertificateResp, err := m.sdkClient.ImportCertificate(ctx, importCertificateReq) m.logger.Debug("sdk request 'acm.ImportCertificate'", slog.Any("request", importCertificateReq), slog.Any("response", importCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'acm.ImportCertificate': %w", err) diff --git a/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go b/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go index 65e687836..e4cdee8da 100644 --- a/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go +++ b/pkg/core/ssl-manager/providers/aws-iam/aws_iam.go @@ -92,7 +92,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if m.config.CertificatePath != "" { listServerCertificatesReq.PathPrefix = aws.String(m.config.CertificatePath) } - listServerCertificatesResp, err := m.sdkClient.ListServerCertificates(context.TODO(), listServerCertificatesReq) + listServerCertificatesResp, err := m.sdkClient.ListServerCertificates(ctx, listServerCertificatesReq) m.logger.Debug("sdk request 'iam.ListServerCertificates'", slog.Any("request", listServerCertificatesReq), slog.Any("response", listServerCertificatesResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'iam.ListServerCertificates': %w", err) @@ -113,7 +113,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey getServerCertificateReq := &awsiam.GetServerCertificateInput{ ServerCertificateName: certItem.ServerCertificateName, } - getServerCertificateResp, err := m.sdkClient.GetServerCertificate(context.TODO(), getServerCertificateReq) + getServerCertificateResp, err := m.sdkClient.GetServerCertificate(ctx, getServerCertificateReq) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'iam.GetServerCertificate': %w", err) } else { @@ -152,7 +152,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey if m.config.CertificatePath == "" { uploadServerCertificateReq.Path = aws.String("/") } - uploadServerCertificateResp, err := m.sdkClient.UploadServerCertificate(context.TODO(), uploadServerCertificateReq) + uploadServerCertificateResp, err := m.sdkClient.UploadServerCertificate(ctx, uploadServerCertificateReq) m.logger.Debug("sdk request 'iam.UploadServerCertificate'", slog.Any("request", uploadServerCertificateReq), slog.Any("response", uploadServerCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'iam.UploadServerCertificate': %w", err) diff --git a/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go b/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go index e5aabcc76..0087348a3 100644 --- a/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go +++ b/pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go @@ -113,7 +113,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey } // 对比证书内容 - getCertificateResp, err := m.sdkClient.GetCertificate(context.TODO(), certItem.ID.Name(), certItem.ID.Version(), nil) + getCertificateResp, err := m.sdkClient.GetCertificate(ctx, certItem.ID.Name(), certItem.ID.Version(), nil) m.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", certItem.ID.Name()), slog.String("request.certificateVersion", certItem.ID.Version()), slog.Any("response", getCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'keyvault.GetCertificate': %w", err) @@ -157,7 +157,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey TAG_CERTSN: to.Ptr(certSN), }, } - importCertificateResp, err := m.sdkClient.ImportCertificate(context.TODO(), certName, importCertificateParams, nil) + importCertificateResp, err := m.sdkClient.ImportCertificate(ctx, certName, importCertificateParams, nil) m.logger.Debug("sdk request 'keyvault.ImportCertificate'", slog.String("request.certificateName", certName), slog.Any("request.parameters", importCertificateParams), slog.Any("response", importCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'keyvault.ImportCertificate': %w", err) diff --git a/pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go b/pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go index 5b5b6b5af..d455ba279 100644 --- a/pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go +++ b/pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go @@ -62,7 +62,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey Automated: false, ValidateRootCA: false, } - createCertificateResp, err := m.sdkClient.Create(context.TODO(), createCertificateReq) + createCertificateResp, err := m.sdkClient.Create(ctx, createCertificateReq) m.logger.Debug("sdk request 'sslcerts.Create'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Create': %w", err) diff --git a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go index cc6278289..3c072c3c2 100644 --- a/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go +++ b/pkg/core/ssl-manager/providers/qiniu-sslcert/qiniu_sslcert.go @@ -76,7 +76,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey default: } - getSslCertListResp, err := m.sdkClient.GetSslCertList(context.TODO(), getSslCertListMarker, 200) + getSslCertListResp, err := m.sdkClient.GetSslCertList(ctx, getSslCertListMarker, 200) m.logger.Debug("sdk request 'sslcert.GetList'", slog.Any("request.marker", getSslCertListMarker), slog.Any("response", getSslCertListResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcert.GetList': %w", err) @@ -134,7 +134,7 @@ func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkey // 上传新证书 // REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate - uploadSslCertResp, err := m.sdkClient.UploadSslCert(context.TODO(), certName, certX509.Subject.CommonName, certPEM, privkeyPEM) + uploadSslCertResp, err := m.sdkClient.UploadSslCert(ctx, certName, certX509.Subject.CommonName, certPEM, privkeyPEM) m.logger.Debug("sdk request 'sslcert.Upload'", slog.Any("response", uploadSslCertResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'sslcert.Upload': %w", err) From 3722ab0e01785d01c015f708991041dc5ea46f17 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:04 +0800 Subject: [PATCH 17/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to aliyun ddospro --- .../certdeploy/deployers/sp_aliyun_ddospro.go | 11 +- .../aliyun-ddospro/aliyun_ddospro.go | 125 ++++++++++++++++-- .../aliyun-ddospro/aliyun_ddospro_test.go | 9 +- .../providers/aliyun-ddospro/consts.go | 10 ++ .../aliyun-ddospro/internal/client.go | 38 ++++++ ...yNodeConfigFieldsProviderAliyunDDoSPro.tsx | 71 ++++++++-- 6 files changed, 238 insertions(+), 26 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/aliyun-ddospro/consts.go diff --git a/internal/certdeploy/deployers/sp_aliyun_ddospro.go b/internal/certdeploy/deployers/sp_aliyun_ddospro.go index 906cafece..7271794e6 100644 --- a/internal/certdeploy/deployers/sp_aliyun_ddospro.go +++ b/internal/certdeploy/deployers/sp_aliyun_ddospro.go @@ -17,11 +17,12 @@ func init() { } provider, err := aliyunddospro.NewSSLDeployerProvider(&aliyunddospro.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go index b69cabe5c..1984de8d6 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro.go @@ -16,6 +16,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-ddospro/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -27,6 +29,9 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 网站域名(支持泛域名)。 Domain string `json:"domain"` } @@ -81,10 +86,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -93,19 +94,127 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + 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("could not find any 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("could not find any 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 ddoscoo domains to deploy") + } else { + d.logger.Info("found ddoscoo domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + certId := upres.ExtendedData["CertIdentifier"].(string) + if err := d.updateDomainCertificate(ctx, domain, certId); 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://help.aliyun.com/zh/anti-ddos/anti-ddos-pro-and-premium/developer-reference/api-ddoscoo-2020-01-01-describedomains + describeDomainsReq := &aliddoscoo.DescribeDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + } + describeDomainsResp, err := d.sdkClient.DescribeDomainsWithContext(ctx, describeDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'aliddoscoo.DescribeLiveUserDomains'", slog.Any("request", describeDomainsReq), slog.Any("response", describeDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'aliddoscoo.DescribeDomains': %w", err) + } + + for _, domain := range describeDomainsResp.Body.Domains { + domains = append(domains, tea.StringValue(domain)) + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 为网站业务转发规则关联 SSL 证书 // REF: https://help.aliyun.com/zh/anti-ddos/anti-ddos-pro-and-premium/developer-reference/api-ddoscoo-2020-01-01-associatewebcert associateWebCertReq := &aliddoscoo.AssociateWebCertRequest{ - Domain: tea.String(d.config.Domain), - CertIdentifier: tea.String(upres.ExtendedData["CertIdentifier"].(string)), + Domain: tea.String(domain), + CertIdentifier: tea.String(cloudCertId), } associateWebCertResp, err := d.sdkClient.AssociateWebCertWithContext(ctx, associateWebCertReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'dcdn.AssociateWebCert'", slog.Any("request", associateWebCertReq), slog.Any("response", associateWebCertResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'dcdn.AssociateWebCert': %w", err) + return fmt.Errorf("failed to execute sdk request 'dcdn.AssociateWebCert': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret, region string) (*internal.DdoscooClient, error) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro_test.go b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro_test.go index 458e1928a..aec9989a6 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ddospro/aliyun_ddospro_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddospro/consts.go b/pkg/core/ssl-deployer/providers/aliyun-ddospro/consts.go new file mode 100644 index 000000000..2c25f12d6 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-ddospro/consts.go @@ -0,0 +1,10 @@ +package aliyunddospro + +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/aliyun-ddospro/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-ddospro/internal/client.go index 837d3507b..989e0831f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ddospro/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ddospro/internal/client.go @@ -92,3 +92,41 @@ func (client *DdoscooClient) AssociateWebCertWithContext(ctx context.Context, re _err = dara.Convert(_body, &_result) return _result, _err } + +func (client *DdoscooClient) DescribeDomainsWithContext(ctx context.Context, request *aliddoscoo.DescribeDomainsRequest, runtime *dara.RuntimeOptions) (_result *aliddoscoo.DescribeDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.InstanceIds) { + query["InstanceIds"] = request.InstanceIds + } + + if !dara.IsNil(request.ResourceGroupId) { + query["ResourceGroupId"] = request.ResourceGroupId + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeDomains"), + Version: dara.String("2020-01-01"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &aliddoscoo.DescribeDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDDoSPro.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDDoSPro.tsx index 5313a8d2e..4f1fff9b8 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDDoSPro.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDDoSPro.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 BizDeployNodeConfigFieldsProviderAliyunDDoSPro = () => { const { i18n, t } = useTranslation(); @@ -15,8 +20,11 @@ const BizDeployNodeConfigFieldsProviderAliyunDDoSPro = () => { [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 ( <> { + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -44,6 +76,7 @@ const BizDeployNodeConfigFieldsProviderAliyunDDoSPro = () => { const getInitialValues = (): Nullish>> => { return { region: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -51,10 +84,30 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_ddospro_region.placeholder")), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_ddospro_region.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderAliyunDDoSPro, { From 0a14ef3725873d4f885cbef79964581cfffa88b3 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:04 +0800 Subject: [PATCH 18/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to aliyun apigw --- .../certdeploy/deployers/sp_aliyun_apigw.go | 17 +- .../providers/aliyun-apigw/aliyun_apigw.go | 349 +++++++++++++++--- .../aliyun-apigw/aliyun_apigw_test.go | 15 +- .../providers/aliyun-apigw/consts.go | 9 + .../providers/aliyun-apigw/internal/client.go | 42 +++ ...loyNodeConfigFieldsProviderAliyunAPIGW.tsx | 108 ++++-- 6 files changed, 441 insertions(+), 99 deletions(-) diff --git a/internal/certdeploy/deployers/sp_aliyun_apigw.go b/internal/certdeploy/deployers/sp_aliyun_apigw.go index d7fd88816..f457361dc 100644 --- a/internal/certdeploy/deployers/sp_aliyun_apigw.go +++ b/internal/certdeploy/deployers/sp_aliyun_apigw.go @@ -17,14 +17,15 @@ func init() { } provider, err := aliyunapigw.NewSSLDeployerProvider(&aliyunapigw.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - ServiceType: xmaps.GetString(options.ProviderExtendedConfig, "serviceType"), - GatewayId: xmaps.GetString(options.ProviderExtendedConfig, "gatewayId"), - GroupId: xmaps.GetString(options.ProviderExtendedConfig, "groupId"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + ServiceType: xmaps.GetString(options.ProviderExtendedConfig, "serviceType"), + GatewayId: xmaps.GetString(options.ProviderExtendedConfig, "gatewayId"), + GroupId: xmaps.GetString(options.ProviderExtendedConfig, "groupId"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go index 97e181890..62386898a 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -18,6 +18,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-apigw/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -37,6 +39,9 @@ type SSLDeployerProviderConfig struct { // API 分组 ID。 // 服务类型为 [SERVICE_TYPE_TRADITIONAL] 时必填。 GroupId string `json:"groupId,omitempty"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 自定义域名(支持泛域名)。 Domain string `json:"domain"` } @@ -116,15 +121,220 @@ func (d *SSLDeployerProvider) deployToTraditional(ctx context.Context, certPEM s if d.config.GroupId == "" { return errors.New("config `groupId` is required") } - if d.config.Domain == "" { - return errors.New("config `domain` is required") + + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getTraditionalAllDomains(ctx, d.config.GroupId) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + domainCandidates, err := d.getTraditionalAllDomains(ctx, d.config.GroupId) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by certificate") + } + } + + default: + return fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no apigw domains to deploy") + } else { + d.logger.Info("found apigw domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateTraditionalDomainCertificate(ctx, d.config.GroupId, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.GatewayId == "" { + return errors.New("config `gatewayId` is required") + } + + // 上传证书 + upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getCloudNativeAllDomains(ctx, d.config.GatewayId) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + domainCandidates, err := d.getCloudNativeAllDomains(ctx, d.config.GatewayId) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by certificate") + } + } + + default: + return fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no apigw domains to deploy") + } else { + d.logger.Info("found apigw domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return ctx.Err() + default: + certId := upres.ExtendedData["CertIdentifier"].(string) + if err := d.updateCloudNativeDomainCertificate(ctx, d.config.GatewayId, domain, certId); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *SSLDeployerProvider) getTraditionalAllDomains(ctx context.Context, cloudGroupId string) ([]string, error) { + domains := make([]string, 0) + + // 查询 API 分组详情 + // REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-describeapigroup + describeApiGroupReq := &alicloudapi.DescribeApiGroupRequest{ + GroupId: tea.String(cloudGroupId), + } + describeApiGroupResp, err := d.sdkClients.TraditionalAPIGateway.DescribeApiGroupWithContext(ctx, describeApiGroupReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apigateway.DescribeApiGroup'", slog.Any("request", describeApiGroupReq), slog.Any("response", describeApiGroupResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'apigateway.DescribeApiGroup': %w", err) + } + + for _, domainItem := range describeApiGroupResp.Body.CustomDomains.DomainItem { + if strings.EqualFold(tea.StringValue(domainItem.DomainBindingStatus), "BINDING") { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateTraditionalDomainCertificate(ctx context.Context, cloudGroupId string, domain string, certPEM, privkeyPEM string) error { // 为自定义域名添加 SSL 证书 // REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-setdomaincertificate setDomainCertificateReq := &alicloudapi.SetDomainCertificateRequest{ - GroupId: tea.String(d.config.GroupId), - DomainName: tea.String(d.config.Domain), + GroupId: tea.String(cloudGroupId), + DomainName: tea.String(domain), CertificateName: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())), CertificateBody: tea.String(certPEM), CertificatePrivateKey: tea.String(privkeyPEM), @@ -138,59 +348,53 @@ func (d *SSLDeployerProvider) deployToTraditional(ctx context.Context, certPEM s return nil } -func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM string, privkeyPEM string) error { - if d.config.GatewayId == "" { - return errors.New("config `gatewayId` is required") - } - if d.config.Domain == "" { - return errors.New("config `domain` is required") +func (d *SSLDeployerProvider) getCloudNativeAllDomains(ctx context.Context, cloudGatewayId string) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains + listDomainsPageNumber := 1 + listDomainsPageSize := 10 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listDomainsReq := &aliapig.ListDomainsRequest{ + ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + GatewayId: tea.String(cloudGatewayId), + PageNumber: tea.Int32(int32(listDomainsPageNumber)), + PageSize: tea.Int32(int32(listDomainsPageSize)), + } + listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomainsWithContext(ctx, listDomainsReq, make(map[string]*string), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'apig.ListDomains': %w", err) + } + + if listDomainsResp.Body == nil || listDomainsResp.Body.Data == nil { + break + } + + for _, domainItem := range listDomainsResp.Body.Data.Items { + if strings.EqualFold(tea.StringValue(domainItem.Status), "Published") { + domains = append(domains, tea.StringValue(domainItem.Name)) + } + } + + if len(listDomainsResp.Body.Data.Items) < listDomainsPageSize { + break + } + + listDomainsPageNumber++ } - // 获取域名 ID - domainId, err := d.findCloudNativeDomainIdByDomain(ctx, d.config.Domain) - if err != nil { - return err - } - - // 查询域名 - // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain - getDomainReq := &aliapig.GetDomainRequest{} - getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomainWithContext(ctx, tea.String(domainId), getDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'apig.GetDomain'", slog.String("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'apig.GetDomain': %w", err) - } - - // 上传证书 - upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) - if err != nil { - return fmt.Errorf("failed to upload certificate file: %w", err) - } else { - d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) - } - - // 更新域名 - // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-updatedomain - updateDomainReq := &aliapig.UpdateDomainRequest{ - Protocol: tea.String("HTTPS"), - ForceHttps: getDomainResp.Body.Data.ForceHttps, - MTLSEnabled: getDomainResp.Body.Data.MTLSEnabled, - Http2Option: getDomainResp.Body.Data.Http2Option, - TlsMin: getDomainResp.Body.Data.TlsMin, - TlsMax: getDomainResp.Body.Data.TlsMax, - TlsCipherSuitesConfig: getDomainResp.Body.Data.TlsCipherSuitesConfig, - CertIdentifier: tea.String(upres.ExtendedData["CertIdentifier"].(string)), - } - updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomainWithContext(ctx, tea.String(domainId), updateDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'apig.UpdateDomain'", slog.String("domainId", domainId), slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'apig.UpdateDomain': %w", err) - } - - return nil + return domains, nil } -func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Context, domain string) (string, error) { +func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Context, cloudGatewayId string, domain string) (string, error) { // 查询域名列表 // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains listDomainsPageNumber := 1 @@ -204,7 +408,7 @@ func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Contex listDomainsReq := &aliapig.ListDomainsRequest{ ResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), - GatewayId: tea.String(d.config.GatewayId), + GatewayId: tea.String(cloudGatewayId), NameLike: tea.String(domain), PageNumber: tea.Int32(int32(listDomainsPageNumber)), PageSize: tea.Int32(int32(listDomainsPageSize)), @@ -235,6 +439,43 @@ func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Contex return "", fmt.Errorf("could not find domain '%s'", domain) } +func (d *SSLDeployerProvider) updateCloudNativeDomainCertificate(ctx context.Context, cloudGatewayId string, domain string, cloudCertId string) error { + // 获取域名 ID + domainId, err := d.findCloudNativeDomainIdByDomain(ctx, cloudGatewayId, domain) + if err != nil { + return err + } + + // 查询域名 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain + getDomainReq := &aliapig.GetDomainRequest{} + getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomainWithContext(ctx, tea.String(domainId), getDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apig.GetDomain'", slog.String("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'apig.GetDomain': %w", err) + } + + // 更新域名 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-updatedomain + updateDomainReq := &aliapig.UpdateDomainRequest{ + Protocol: tea.String("HTTPS"), + ForceHttps: getDomainResp.Body.Data.ForceHttps, + MTLSEnabled: getDomainResp.Body.Data.MTLSEnabled, + Http2Option: getDomainResp.Body.Data.Http2Option, + TlsMin: getDomainResp.Body.Data.TlsMin, + TlsMax: getDomainResp.Body.Data.TlsMax, + TlsCipherSuitesConfig: getDomainResp.Body.Data.TlsCipherSuitesConfig, + CertIdentifier: tea.String(cloudCertId), + } + updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomainWithContext(ctx, tea.String(domainId), updateDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apig.UpdateDomain'", slog.String("domainId", domainId), slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'apig.UpdateDomain': %w", err) + } + + return nil +} + func createSDKClients(accessKeyId, accessKeySecret, region string) (*wSDKClients, error) { // 接入点一览 https://api.aliyun.com/product/APIG var cloudNativeAPIGEndpoint string diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw_test.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw_test.go index ef522b00d..2ed1b0527 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw_test.go @@ -69,13 +69,14 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - ServiceType: fServiceType, - GatewayId: fGatewayId, - GroupId: fGroupId, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ServiceType: fServiceType, + GatewayId: fGatewayId, + GroupId: fGroupId, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/consts.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/consts.go index 7a767b08b..a37be7845 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/consts.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/consts.go @@ -6,3 +6,12 @@ const ( // 服务类型:云原生 API 网关。 SERVICE_TYPE_CLOUDNATIVE = "cloudnative" ) + +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/aliyun-apigw/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/internal/client.go index b737a5252..b5d80a4e7 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/internal/client.go @@ -197,6 +197,48 @@ func NewCloudapiClient(config *openapiutil.Config) (*CloudapiClient, error) { return client, err } +func (client *CloudapiClient) DescribeApiGroupWithContext(ctx context.Context, request *alicloudapi.DescribeApiGroupRequest, runtime *dara.RuntimeOptions) (_result *alicloudapi.DescribeApiGroupResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.GroupId) { + query["GroupId"] = request.GroupId + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeApiGroup"), + Version: dara.String("2016-07-14"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alicloudapi.DescribeApiGroupResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *CloudapiClient) SetDomainCertificateWithContext(ctx context.Context, request *alicloudapi.SetDomainCertificateRequest, runtime *dara.RuntimeOptions) (_result *alicloudapi.SetDomainCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx index 9abd5ce5a..78e63827a 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx @@ -1,5 +1,5 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form, Input, Select } from "antd"; +import { Form, Input, Radio, Select } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -11,6 +11,10 @@ import { useFormNestedFieldsContext } from "./_context"; const SERVICE_TYPE_CLOUDNATIVE = "cloudnative" as const; const SERVICE_TYPE_TRADITIONAL = "traditional" as const; +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 BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => { const { i18n, t } = useTranslation(); @@ -23,6 +27,7 @@ const BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => { const initialValues = getInitialValues(); const fieldServiceType = Form.useWatch([parentNamePath, "serviceType"], formInst); + const fieldDomainMatchPattern = Form.useWatch([parentNamePath, "domainMatchPattern"], { form: formInst, preserve: true }); return ( <> @@ -77,13 +82,37 @@ const BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => { + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -91,6 +120,8 @@ const BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => { const getInitialValues = (): Nullish>> => { return { region: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, + domain: "", }; }; @@ -103,36 +134,53 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_apigw_region.placeholder")), gatewayId: z.string().nullish(), groupId: z.string().nullish(), - domain: z - .string() - .nonempty(t("workflow_node.deploy.form.aliyun_apigw_domain.placeholder")) - .refine((v) => validDomainName(v!, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), }) .superRefine((values, ctx) => { - switch (values.serviceType) { - case SERVICE_TYPE_CLOUDNATIVE: - { - if (!values.gatewayId?.trim()) { - ctx.addIssue({ - code: "custom", - message: t("workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder"), - path: ["gatewayId"], - }); + if (values.serviceType) { + switch (values.serviceType) { + case SERVICE_TYPE_CLOUDNATIVE: + { + if (!values.gatewayId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder"), + path: ["gatewayId"], + }); + } } - } - break; + break; - case SERVICE_TYPE_TRADITIONAL: - { - if (!values.groupId?.trim()) { - ctx.addIssue({ - code: "custom", - message: t("workflow_node.deploy.form.aliyun_apigw_group_id.placeholder"), - path: ["groupId"], - }); + case SERVICE_TYPE_TRADITIONAL: + { + if (!values.groupId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_apigw_group_id.placeholder"), + path: ["groupId"], + }); + } } - } - break; + break; + } + } + + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } } }); }; From b18fccfeca1650ab5ce7821d7574b6d93e3a24ed Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:27:04 +0800 Subject: [PATCH 19/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to aliyun fc --- internal/certdeploy/deployers/sp_aliyun_fc.go | 13 +- .../providers/aliyun-apigw/aliyun_apigw.go | 112 +++---- .../providers/aliyun-fc/aliyun_fc.go | 283 +++++++++++++++++- .../providers/aliyun-fc/aliyun_fc_test.go | 10 +- .../providers/aliyun-fc/consts.go | 10 + .../providers/aliyun-fc/internal/client.go | 119 ++++++++ .../providers/aliyun-waf/aliyun_waf.go | 12 +- .../huaweicloud-cdn/huaweicloud_cdn.go | 4 +- ...DeployNodeConfigFieldsProviderAliyunFC.tsx | 73 ++++- 9 files changed, 541 insertions(+), 95 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/aliyun-fc/consts.go diff --git a/internal/certdeploy/deployers/sp_aliyun_fc.go b/internal/certdeploy/deployers/sp_aliyun_fc.go index 76e2d5d16..017e2252b 100644 --- a/internal/certdeploy/deployers/sp_aliyun_fc.go +++ b/internal/certdeploy/deployers/sp_aliyun_fc.go @@ -17,12 +17,13 @@ func init() { } provider, err := aliyunfc.NewSSLDeployerProvider(&aliyunfc.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - ServiceVersion: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + ServiceVersion: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go index 62386898a..b5e024d7f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -329,25 +329,6 @@ func (d *SSLDeployerProvider) getTraditionalAllDomains(ctx context.Context, clou return domains, nil } -func (d *SSLDeployerProvider) updateTraditionalDomainCertificate(ctx context.Context, cloudGroupId string, domain string, certPEM, privkeyPEM string) error { - // 为自定义域名添加 SSL 证书 - // REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-setdomaincertificate - setDomainCertificateReq := &alicloudapi.SetDomainCertificateRequest{ - GroupId: tea.String(cloudGroupId), - DomainName: tea.String(domain), - CertificateName: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())), - CertificateBody: tea.String(certPEM), - CertificatePrivateKey: tea.String(privkeyPEM), - } - setDomainCertificateResp, err := d.sdkClients.TraditionalAPIGateway.SetDomainCertificateWithContext(ctx, setDomainCertificateReq, &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'apigateway.SetDomainCertificate'", slog.Any("request", setDomainCertificateReq), slog.Any("response", setDomainCertificateResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'apigateway.SetDomainCertificate': %w", err) - } - - return nil -} - func (d *SSLDeployerProvider) getCloudNativeAllDomains(ctx context.Context, cloudGatewayId string) ([]string, error) { domains := make([]string, 0) @@ -394,6 +375,62 @@ func (d *SSLDeployerProvider) getCloudNativeAllDomains(ctx context.Context, clou return domains, nil } +func (d *SSLDeployerProvider) updateTraditionalDomainCertificate(ctx context.Context, cloudGroupId string, domain string, certPEM, privkeyPEM string) error { + // 为自定义域名添加 SSL 证书 + // REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-setdomaincertificate + setDomainCertificateReq := &alicloudapi.SetDomainCertificateRequest{ + GroupId: tea.String(cloudGroupId), + DomainName: tea.String(domain), + CertificateName: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())), + CertificateBody: tea.String(certPEM), + CertificatePrivateKey: tea.String(privkeyPEM), + } + setDomainCertificateResp, err := d.sdkClients.TraditionalAPIGateway.SetDomainCertificateWithContext(ctx, setDomainCertificateReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apigateway.SetDomainCertificate'", slog.Any("request", setDomainCertificateReq), slog.Any("response", setDomainCertificateResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'apigateway.SetDomainCertificate': %w", err) + } + + return nil +} + +func (d *SSLDeployerProvider) updateCloudNativeDomainCertificate(ctx context.Context, cloudGatewayId string, domain string, cloudCertId string) error { + // 获取域名 ID + domainId, err := d.findCloudNativeDomainIdByDomain(ctx, cloudGatewayId, domain) + if err != nil { + return err + } + + // 查询域名 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain + getDomainReq := &aliapig.GetDomainRequest{} + getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomainWithContext(ctx, tea.String(domainId), getDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apig.GetDomain'", slog.String("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'apig.GetDomain': %w", err) + } + + // 更新域名 + // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-updatedomain + updateDomainReq := &aliapig.UpdateDomainRequest{ + Protocol: tea.String("HTTPS"), + ForceHttps: getDomainResp.Body.Data.ForceHttps, + MTLSEnabled: getDomainResp.Body.Data.MTLSEnabled, + Http2Option: getDomainResp.Body.Data.Http2Option, + TlsMin: getDomainResp.Body.Data.TlsMin, + TlsMax: getDomainResp.Body.Data.TlsMax, + TlsCipherSuitesConfig: getDomainResp.Body.Data.TlsCipherSuitesConfig, + CertIdentifier: tea.String(cloudCertId), + } + updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomainWithContext(ctx, tea.String(domainId), updateDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'apig.UpdateDomain'", slog.String("domainId", domainId), slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'apig.UpdateDomain': %w", err) + } + + return nil +} + func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Context, cloudGatewayId string, domain string) (string, error) { // 查询域名列表 // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains @@ -439,43 +476,6 @@ func (d *SSLDeployerProvider) findCloudNativeDomainIdByDomain(ctx context.Contex return "", fmt.Errorf("could not find domain '%s'", domain) } -func (d *SSLDeployerProvider) updateCloudNativeDomainCertificate(ctx context.Context, cloudGatewayId string, domain string, cloudCertId string) error { - // 获取域名 ID - domainId, err := d.findCloudNativeDomainIdByDomain(ctx, cloudGatewayId, domain) - if err != nil { - return err - } - - // 查询域名 - // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain - getDomainReq := &aliapig.GetDomainRequest{} - getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomainWithContext(ctx, tea.String(domainId), getDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'apig.GetDomain'", slog.String("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'apig.GetDomain': %w", err) - } - - // 更新域名 - // REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-updatedomain - updateDomainReq := &aliapig.UpdateDomainRequest{ - Protocol: tea.String("HTTPS"), - ForceHttps: getDomainResp.Body.Data.ForceHttps, - MTLSEnabled: getDomainResp.Body.Data.MTLSEnabled, - Http2Option: getDomainResp.Body.Data.Http2Option, - TlsMin: getDomainResp.Body.Data.TlsMin, - TlsMax: getDomainResp.Body.Data.TlsMax, - TlsCipherSuitesConfig: getDomainResp.Body.Data.TlsCipherSuitesConfig, - CertIdentifier: tea.String(cloudCertId), - } - updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomainWithContext(ctx, tea.String(domainId), updateDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) - d.logger.Debug("sdk request 'apig.UpdateDomain'", slog.String("domainId", domainId), slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) - if err != nil { - return fmt.Errorf("failed to execute sdk request 'apig.UpdateDomain': %w", err) - } - - return nil -} - func createSDKClients(accessKeyId, accessKeySecret, region string) (*wSDKClients, error) { // 接入点一览 https://api.aliyun.com/product/APIG var cloudNativeAPIGEndpoint string diff --git a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go index aca2872cc..7f595c94f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go +++ b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "strings" "time" aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" @@ -12,9 +13,12 @@ import ( alifc2 "github.com/alibabacloud-go/fc-open-20210406/v2/client" "github.com/alibabacloud-go/tea/dara" "github.com/alibabacloud-go/tea/tea" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-fc/internal" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -29,6 +33,9 @@ type SSLDeployerProviderConfig struct { // 服务版本。 // 可取值 "2.0"、"3.0"。 ServiceVersion string `json:"serviceVersion"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 自定义域名(支持泛域名)。 Domain string `json:"domain"` } @@ -91,16 +98,270 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } func (d *SSLDeployerProvider) deployToFC3(ctx context.Context, certPEM string, privkeyPEM string) error { - if d.config.Domain == "" { - return errors.New("config `domain` is required") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getFC3AllDomains(ctx) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + domainCandidates, err := d.getFC3AllDomains(ctx) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by certificate") + } + } + + default: + return fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no fc domains to deploy") + } else { + d.logger.Info("found fc domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateFC3DomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *SSLDeployerProvider) deployToFC2(ctx context.Context, certPEM string, privkeyPEM string) error { + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getFC2AllDomains(ctx) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + domainCandidates, err := d.getFC2AllDomains(ctx) + if err != nil { + return err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return errors.New("could not find any domains matched by certificate") + } + } + + default: + return fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no fc domains to deploy") + } else { + d.logger.Info("found fc domains to deploy", slog.Any("domains", domains)) + var errs []error + + for _, domain := range domains { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateFC2DomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + return nil +} + +func (d *SSLDeployerProvider) getFC3AllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 列出自定义域名 + // REF: https://help.aliyun.com/zh/functioncompute/fc/developer-reference/api-fc-2023-03-30-listcustomdomains + listCustomDomainsNextToken := (*string)(nil) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listCustomDomainsReq := &alifc3.ListCustomDomainsRequest{ + NextToken: listCustomDomainsNextToken, + Limit: tea.Int32(100), + } + listCustomDomainsResp, err := d.sdkClients.FC3.ListCustomDomainsWithContext(ctx, listCustomDomainsReq, make(map[string]*string, 0), &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'fc.ListCustomDomains'", slog.Any("request", listCustomDomainsReq), slog.Any("response", listCustomDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'fc.ListCustomDomains': %w", err) + } + + if listCustomDomainsResp.Body == nil { + break + } + + for _, domainItem := range listCustomDomainsResp.Body.CustomDomains { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(listCustomDomainsResp.Body.CustomDomains) == 0 || listCustomDomainsResp.Body.NextToken == nil { + break + } + + listCustomDomainsNextToken = listCustomDomainsResp.Body.NextToken + } + + return domains, nil +} + +func (d *SSLDeployerProvider) getFC2AllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 列出自定义域名 + // REF: https://help.aliyun.com/zh/functioncompute/fc-2-0/developer-reference/api-fc-open-2021-04-06-listcustomdomains + listCustomDomainsNextToken := (*string)(nil) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listCustomDomainsReq := &alifc2.ListCustomDomainsRequest{ + NextToken: listCustomDomainsNextToken, + Limit: tea.Int32(100), + } + listCustomDomainsResp, err := d.sdkClients.FC2.ListCustomDomains(listCustomDomainsReq) + d.logger.Debug("sdk request 'fc.ListCustomDomains'", slog.Any("request", listCustomDomainsReq), slog.Any("response", listCustomDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'fc.ListCustomDomains': %w", err) + } + + if listCustomDomainsResp.Body == nil { + break + } + + for _, domainItem := range listCustomDomainsResp.Body.CustomDomains { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(listCustomDomainsResp.Body.CustomDomains) == 0 || listCustomDomainsResp.Body.NextToken == nil { + break + } + + listCustomDomainsNextToken = listCustomDomainsResp.Body.NextToken + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateFC3DomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 获取自定义域名 // REF: https://help.aliyun.com/zh/functioncompute/fc-3-0/developer-reference/api-fc-2023-03-30-getcustomdomain - getCustomDomainResp, err := d.sdkClients.FC3.GetCustomDomainWithContext(ctx, tea.String(d.config.Domain), make(map[string]*string), &dara.RuntimeOptions{}) + getCustomDomainResp, err := d.sdkClients.FC3.GetCustomDomainWithContext(ctx, tea.String(domain), make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'fc.GetCustomDomain'", slog.Any("response", getCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.GetCustomDomain': %w", err) + } else { + if getCustomDomainResp.Body.CertConfig != nil && tea.StringValue(getCustomDomainResp.Body.CertConfig.Certificate) == certPEM { + return nil + } } // 更新自定义域名 @@ -119,7 +380,7 @@ func (d *SSLDeployerProvider) deployToFC3(ctx context.Context, certPEM string, p if tea.StringValue(updateCustomDomainReq.Body.Protocol) == "HTTP" { updateCustomDomainReq.Body.Protocol = tea.String("HTTP,HTTPS") } - updateCustomDomainResp, err := d.sdkClients.FC3.UpdateCustomDomainWithContext(ctx, tea.String(d.config.Domain), updateCustomDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) + updateCustomDomainResp, err := d.sdkClients.FC3.UpdateCustomDomainWithContext(ctx, tea.String(domain), updateCustomDomainReq, make(map[string]*string), &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'fc.UpdateCustomDomain'", slog.Any("request", updateCustomDomainReq), slog.Any("response", updateCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.UpdateCustomDomain': %w", err) @@ -128,17 +389,17 @@ func (d *SSLDeployerProvider) deployToFC3(ctx context.Context, certPEM string, p return nil } -func (d *SSLDeployerProvider) deployToFC2(ctx context.Context, certPEM string, privkeyPEM string) error { - if d.config.Domain == "" { - return errors.New("config `domain` is required") - } - +func (d *SSLDeployerProvider) updateFC2DomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 获取自定义域名 // REF: https://help.aliyun.com/zh/functioncompute/fc-2-0/developer-reference/api-fc-open-2021-04-06-getcustomdomain - getCustomDomainResp, err := d.sdkClients.FC2.GetCustomDomain(tea.String(d.config.Domain)) + getCustomDomainResp, err := d.sdkClients.FC2.GetCustomDomain(tea.String(domain)) d.logger.Debug("sdk request 'fc.GetCustomDomain'", slog.Any("response", getCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.GetCustomDomain': %w", err) + } else { + if getCustomDomainResp.Body.CertConfig != nil && tea.StringValue(getCustomDomainResp.Body.CertConfig.Certificate) == certPEM { + return nil + } } // 更新自定义域名 @@ -155,7 +416,7 @@ func (d *SSLDeployerProvider) deployToFC2(ctx context.Context, certPEM string, p if tea.StringValue(updateCustomDomainReq.Protocol) == "HTTP" { updateCustomDomainReq.Protocol = tea.String("HTTP,HTTPS") } - updateCustomDomainResp, err := d.sdkClients.FC2.UpdateCustomDomain(tea.String(d.config.Domain), updateCustomDomainReq) + updateCustomDomainResp, err := d.sdkClients.FC2.UpdateCustomDomain(tea.String(domain), updateCustomDomainReq) d.logger.Debug("sdk request 'fc.UpdateCustomDomain'", slog.Any("request", updateCustomDomainReq), slog.Any("response", updateCustomDomainResp)) if err != nil { return fmt.Errorf("failed to execute sdk request 'fc.UpdateCustomDomain': %w", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc_test.go b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc_test.go index de03a75d2..e33ec12ee 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-fc/aliyun_fc_test.go @@ -57,10 +57,12 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ServiceVersion: "3.0", + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-fc/consts.go b/pkg/core/ssl-deployer/providers/aliyun-fc/consts.go new file mode 100644 index 000000000..3365c700f --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-fc/consts.go @@ -0,0 +1,10 @@ +package aliyunfc + +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/aliyun-fc/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-fc/internal/client.go index 3270dc2e5..923c9fdec 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-fc/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-fc/internal/client.go @@ -63,6 +63,49 @@ func (client *FcClient) GetCustomDomainWithContext(ctx context.Context, domainNa return _result, _err } +func (client *FcClient) ListCustomDomainsWithContext(ctx context.Context, request *alifc.ListCustomDomainsRequest, headers map[string]*string, runtime *dara.RuntimeOptions) (_result *alifc.ListCustomDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.Limit) { + query["limit"] = request.Limit + } + + if !dara.IsNil(request.NextToken) { + query["nextToken"] = request.NextToken + } + + if !dara.IsNil(request.Prefix) { + query["prefix"] = request.Prefix + } + + req := &openapiutilv2.OpenApiRequest{ + Headers: headers, + Query: openapiutil.Query(query), + } + params := &openapiutilv2.Params{ + Action: dara.String("ListCustomDomains"), + Version: dara.String("2023-03-30"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/2023-03-30/custom-domains"), + Method: dara.String("GET"), + AuthType: dara.String("AK"), + Style: dara.String("ROA"), + ReqBodyType: dara.String("json"), + BodyType: dara.String("json"), + } + _result = &alifc.ListCustomDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *FcClient) UpdateCustomDomainWithContext(ctx context.Context, domainName *string, request *alifc.UpdateCustomDomainRequest, headers map[string]*string, runtime *dara.RuntimeOptions) (_result *alifc.UpdateCustomDomainResponse, _err error) { _err = request.Validate() if _err != nil { @@ -172,6 +215,82 @@ func (client *FcopenClient) GetCustomDomainWithOptions(domainName *string, heade return _result, _err } +func (client *FcopenClient) ListCustomDomains(request *alifcopen.ListCustomDomainsRequest) (_result *alifcopen.ListCustomDomainsResponse, _err error) { + runtime := &util.RuntimeOptions{} + headers := &alifcopen.ListCustomDomainsHeaders{} + _result = &alifcopen.ListCustomDomainsResponse{} + _body, _err := client.ListCustomDomainsWithOptions(request, headers, runtime) + if _err != nil { + return _result, _err + } + _result = _body + return _result, _err +} + +func (client *FcopenClient) ListCustomDomainsWithOptions(request *alifcopen.ListCustomDomainsRequest, headers *alifcopen.ListCustomDomainsHeaders, runtime *util.RuntimeOptions) (_result *alifcopen.ListCustomDomainsResponse, _err error) { + _err = util.ValidateModel(request) + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !tea.BoolValue(util.IsUnset(request.Limit)) { + query["limit"] = request.Limit + } + + if !tea.BoolValue(util.IsUnset(request.NextToken)) { + query["nextToken"] = request.NextToken + } + + if !tea.BoolValue(util.IsUnset(request.Prefix)) { + query["prefix"] = request.Prefix + } + + if !tea.BoolValue(util.IsUnset(request.StartKey)) { + query["startKey"] = request.StartKey + } + + realHeaders := make(map[string]*string) + if !tea.BoolValue(util.IsUnset(headers.CommonHeaders)) { + realHeaders = headers.CommonHeaders + } + + if !tea.BoolValue(util.IsUnset(headers.XFcAccountId)) { + realHeaders["X-Fc-Account-Id"] = util.ToJSONString(headers.XFcAccountId) + } + + if !tea.BoolValue(util.IsUnset(headers.XFcDate)) { + realHeaders["X-Fc-Date"] = util.ToJSONString(headers.XFcDate) + } + + if !tea.BoolValue(util.IsUnset(headers.XFcTraceId)) { + realHeaders["X-Fc-Trace-Id"] = util.ToJSONString(headers.XFcTraceId) + } + + req := &openapi.OpenApiRequest{ + Headers: realHeaders, + Query: openapiutil.Query(query), + } + params := &openapi.Params{ + Action: tea.String("ListCustomDomains"), + Version: tea.String("2021-04-06"), + Protocol: tea.String("HTTPS"), + Pathname: tea.String("/2021-04-06/custom-domains"), + Method: tea.String("GET"), + AuthType: tea.String("AK"), + Style: tea.String("ROA"), + ReqBodyType: tea.String("json"), + BodyType: tea.String("json"), + } + _result = &alifcopen.ListCustomDomainsResponse{} + _body, _err := client.CallApi(params, req, runtime) + if _err != nil { + return _result, _err + } + _err = tea.Convert(_body, &_result) + return _result, _err +} + func (client *FcopenClient) UpdateCustomDomain(domainName *string, request *alifcopen.UpdateCustomDomainRequest) (_result *alifcopen.UpdateCustomDomainResponse, _err error) { runtime := &util.RuntimeOptions{} headers := &alifcopen.UpdateCustomDomainHeaders{} diff --git a/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go b/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go index 6fb4ecb99..f12321f9f 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go +++ b/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go @@ -118,8 +118,8 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedefaulthttps describeDefaultHttpsReq := &aliwaf.DescribeDefaultHttpsRequest{ ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), - InstanceId: tea.String(d.config.InstanceId), RegionId: tea.String(d.config.Region), + InstanceId: tea.String(d.config.InstanceId), } describeDefaultHttpsResp, err := d.sdkClient.DescribeDefaultHttpsWithContext(ctx, describeDefaultHttpsReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.DescribeDefaultHttps'", slog.Any("request", describeDefaultHttpsReq), slog.Any("response", describeDefaultHttpsResp)) @@ -131,8 +131,8 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-modifydefaulthttps modifyDefaultHttpsReq := &aliwaf.ModifyDefaultHttpsRequest{ ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), - InstanceId: tea.String(d.config.InstanceId), RegionId: tea.String(d.config.Region), + InstanceId: tea.String(d.config.InstanceId), CertId: tea.String(upres.ExtendedData["CertIdentifier"].(string)), TLSVersion: tea.String("tlsv1"), EnableTLSv3: tea.Bool(true), @@ -156,8 +156,8 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, // 查询 CNAME 接入详情 // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedomaindetail describeDomainDetailReq := &aliwaf.DescribeDomainDetailRequest{ - InstanceId: tea.String(d.config.InstanceId), RegionId: tea.String(d.config.Region), + InstanceId: tea.String(d.config.InstanceId), Domain: tea.String(d.config.Domain), } describeDomainDetailResp, err := d.sdkClient.DescribeDomainDetailWithContext(ctx, describeDomainDetailReq, &dara.RuntimeOptions{}) @@ -169,13 +169,13 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, // 修改 CNAME 接入资源 // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-modifydomain modifyDomainReq := &aliwaf.ModifyDomainRequest{ - InstanceId: tea.String(d.config.InstanceId), RegionId: tea.String(d.config.Region), + InstanceId: tea.String(d.config.InstanceId), Domain: tea.String(d.config.Domain), Listen: &aliwaf.ModifyDomainRequestListen{CertId: tea.String(upres.ExtendedData["CertIdentifier"].(string))}, Redirect: &aliwaf.ModifyDomainRequestRedirect{Loadbalance: tea.String("iphash")}, } - modifyDomainReq = assign(modifyDomainReq, describeDomainDetailResp.Body) + modifyDomainReq = _assign(modifyDomainReq, describeDomainDetailResp.Body) modifyDomainResp, err := d.sdkClient.ModifyDomainWithContext(ctx, modifyDomainReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'waf.ModifyDomain'", slog.Any("request", modifyDomainReq), slog.Any("response", modifyDomainResp)) if err != nil { @@ -210,7 +210,7 @@ func createSDKClient(accessKeyId, accessKeySecret, region string) (*internal.Waf return client, nil } -func assign(source *aliwaf.ModifyDomainRequest, target *aliwaf.DescribeDomainDetailResponseBody) *aliwaf.ModifyDomainRequest { +func _assign(source *aliwaf.ModifyDomainRequest, target *aliwaf.DescribeDomainDetailResponseBody) *aliwaf.ModifyDomainRequest { // `ModifyDomain` 中不传的字段表示使用默认值、而非保留原值, // 因此这里需要把原配置中的参数重新赋值回去。 diff --git a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index b71a6135e..a88812443 100644 --- a/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -250,7 +250,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai updateDomainMultiCertificatesReqBodyContent.CertificateType = lo.ToPtr(int32(2)) updateDomainMultiCertificatesReqBodyContent.ScmCertificateId = lo.ToPtr(cloudCertId) updateDomainMultiCertificatesReqBodyContent.CertName = lo.ToPtr(cloudCertName) - updateDomainMultiCertificatesReqBodyContent = assign(updateDomainMultiCertificatesReqBodyContent, showDomainFullConfigResp.Configs) + updateDomainMultiCertificatesReqBodyContent = _assign(updateDomainMultiCertificatesReqBodyContent, showDomainFullConfigResp.Configs) updateDomainMultiCertificatesReq := &hccdnmodel.UpdateDomainMultiCertificatesRequest{ EnterpriseProjectId: lo.EmptyableToPtr(d.config.EnterpriseProjectId), Body: &hccdnmodel.UpdateDomainMultiCertificatesRequestBody{ @@ -296,7 +296,7 @@ func createSDKClient(accessKeyId, secretAccessKey, region string) (*internal.Cdn return client, nil } -func assign(source *hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent, target *hccdnmodel.ConfigsGetBody) *hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent { +func _assign(source *hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent, target *hccdnmodel.ConfigsGetBody) *hccdnmodel.UpdateDomainMultiCertificatesRequestBodyContent { // `UpdateDomainMultiCertificates` 中不传的字段表示使用默认值、而非保留原值, // 因此这里需要把原配置中的参数重新赋值回去。 diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunFC.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunFC.tsx index cd141f708..0925c7b4d 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunFC.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunFC.tsx @@ -1,12 +1,17 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form, Input, Select } from "antd"; +import { Form, Input, Radio, Select } 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 BizDeployNodeConfigFieldsProviderAliyunFC = () => { const { i18n, t } = useTranslation(); @@ -15,8 +20,11 @@ const BizDeployNodeConfigFieldsProviderAliyunFC = () => { [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 ( <> { + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -61,6 +93,7 @@ const getInitialValues = (): Nullish>> => { return { region: "", serviceVersion: "3.0", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -68,11 +101,31 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_fc_region.placeholder")), - serviceVersion: z.literal(["2.0", "3.0"], t("workflow_node.deploy.form.aliyun_fc_service_version.placeholder")), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_fc_region.placeholder")), + serviceVersion: z.literal(["2.0", "3.0"], t("workflow_node.deploy.form.aliyun_fc_service_version.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderAliyunFC, { From 4be788f880d6bd6de45d5cf984acaaa37ddf4438 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 20:54:11 +0800 Subject: [PATCH 20/30] refactor: clean code --- .../certdeploy/deployers/sp_qiniu_kodo.go | 1 + .../certdeploy/deployers/sp_upyun_file.go | 5 +- .../providers/qiniu-kodo/qiniu_kodo.go | 2 + .../providers/qiniu-kodo/qiniu_kodo_test.go | 5 + .../providers/upyun-file/upyun_file.go | 122 ++++++++++++++++++ .../providers/upyun-file/upyun_file_test.go | 80 ++++++++++++ ...eployNodeConfigFieldsProviderQiniuKodo.tsx | 11 ++ ...eployNodeConfigFieldsProviderUpyunFile.tsx | 11 ++ .../i18n/locales/en/nls.workflow.nodes.json | 4 + .../i18n/locales/zh/nls.workflow.nodes.json | 8 +- 10 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/upyun-file/upyun_file.go create mode 100644 pkg/core/ssl-deployer/providers/upyun-file/upyun_file_test.go diff --git a/internal/certdeploy/deployers/sp_qiniu_kodo.go b/internal/certdeploy/deployers/sp_qiniu_kodo.go index 69ba9e264..147e62bce 100644 --- a/internal/certdeploy/deployers/sp_qiniu_kodo.go +++ b/internal/certdeploy/deployers/sp_qiniu_kodo.go @@ -19,6 +19,7 @@ func init() { provider, err := qiniukodo.NewSSLDeployerProvider(&qiniukodo.SSLDeployerProviderConfig{ AccessKey: credentials.AccessKey, SecretKey: credentials.SecretKey, + Bucket: xmaps.GetString(options.ProviderExtendedConfig, "bucket"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/internal/certdeploy/deployers/sp_upyun_file.go b/internal/certdeploy/deployers/sp_upyun_file.go index 1b6b8f9c9..2a82fb9be 100644 --- a/internal/certdeploy/deployers/sp_upyun_file.go +++ b/internal/certdeploy/deployers/sp_upyun_file.go @@ -5,7 +5,7 @@ import ( "github.com/certimate-go/certimate/internal/domain" "github.com/certimate-go/certimate/pkg/core" - upyuncdn "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/upyun-cdn" + upyunfile "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/upyun-file" xmaps "github.com/certimate-go/certimate/pkg/utils/maps" ) @@ -16,9 +16,10 @@ func init() { return nil, fmt.Errorf("failed to populate provider access config: %w", err) } - provider, err := upyuncdn.NewSSLDeployerProvider(&upyuncdn.SSLDeployerProviderConfig{ + provider, err := upyunfile.NewSSLDeployerProvider(&upyunfile.SSLDeployerProviderConfig{ Username: credentials.Username, Password: credentials.Password, + Bucket: xmaps.GetString(options.ProviderExtendedConfig, "bucket"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go index 66dd56019..c9b41d2c9 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go +++ b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo.go @@ -18,6 +18,8 @@ type SSLDeployerProviderConfig struct { AccessKey string `json:"accessKey"` // 七牛云 SecretKey。 SecretKey string `json:"secretKey"` + // 存储桶名。暂时无用。 + Bucket string `json:"bucket"` // 自定义域名(不支持泛域名)。 Domain string `json:"domain"` } diff --git a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go index 3dfcf4561..4b8df52a9 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go +++ b/pkg/core/ssl-deployer/providers/qiniu-kodo/qiniu_kodo_test.go @@ -16,6 +16,7 @@ var ( fInputKeyPath string fAccessKey string fSecretKey string + fBucket string fDomain string ) @@ -26,6 +27,7 @@ func init() { flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "") flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") } @@ -37,6 +39,7 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_QINIUKODO_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_SSLDEPLOYER_QINIUKODO_ACCESSKEY="your-access-key" \ --CERTIMATE_SSLDEPLOYER_QINIUKODO_SECRETKEY="your-secret-key" \ + --CERTIMATE_SSLDEPLOYER_QINIUKODO_BUCKET="your-bucket" \ --CERTIMATE_SSLDEPLOYER_QINIUKODO_DOMAIN="example.com" */ func TestDeploy(t *testing.T) { @@ -49,12 +52,14 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("ACCESSKEY: %v", fAccessKey), fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("BUCKET: %v", fBucket), fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ AccessKey: fAccessKey, SecretKey: fSecretKey, + Bucket: fBucket, Domain: fDomain, }) if err != nil { diff --git a/pkg/core/ssl-deployer/providers/upyun-file/upyun_file.go b/pkg/core/ssl-deployer/providers/upyun-file/upyun_file.go new file mode 100644 index 000000000..5b2d07cf1 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/upyun-file/upyun_file.go @@ -0,0 +1,122 @@ +package upyunfile + +import ( + "context" + "errors" + "fmt" + "log/slog" + + "github.com/samber/lo" + + "github.com/certimate-go/certimate/pkg/core" + sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/upyun-ssl" + upyunsdk "github.com/certimate-go/certimate/pkg/sdk3rd/upyun/console" +) + +type SSLDeployerProviderConfig struct { + // 又拍云账号用户名。 + Username string `json:"username"` + // 又拍云账号密码。 + Password string `json:"password"` + // 存储桶名。暂时无用。 + Bucket string `json:"bucket"` + // 自定义域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type SSLDeployerProvider struct { + config *SSLDeployerProviderConfig + logger *slog.Logger + sdkClient *upyunsdk.Client + sslManager core.SSLManager +} + +var _ core.SSLDeployer = (*SSLDeployerProvider)(nil) + +func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the ssl deployer provider is nil") + } + + client, err := createSDKClient(config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("could not create sdk client: %w", err) + } + + sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{ + Username: config.Username, + Password: config.Password, + }) + if err != nil { + return nil, fmt.Errorf("could not create ssl manager: %w", err) + } + + return &SSLDeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslManager: sslmgr, + }, nil +} + +func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { + if logger == nil { + d.logger = slog.New(slog.DiscardHandler) + } else { + d.logger = logger + } + + d.sslManager.SetLogger(logger) +} + +func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { + // 上传证书 + upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 获取域名证书配置 + getHttpsServiceManagerResp, err := d.sdkClient.GetHttpsServiceManager(d.config.Domain) + d.logger.Debug("sdk request 'console.GetHttpsServiceManager'", slog.String("request.domain", d.config.Domain), slog.Any("response", getHttpsServiceManagerResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'console.GetHttpsServiceManager': %w", err) + } + + // 判断域名是否已启用 HTTPS + // 如果已启用,迁移域名证书;否则,设置新证书 + _, lastCertIndex, _ := lo.FindIndexOf(getHttpsServiceManagerResp.Data.Domains, func(item upyunsdk.HttpsServiceManagerDomain) bool { + return item.Https + }) + if lastCertIndex == -1 { + updateHttpsCertificateManagerReq := &upyunsdk.UpdateHttpsCertificateManagerRequest{ + CertificateId: upres.CertId, + Domain: d.config.Domain, + Https: true, + ForceHttps: true, + } + updateHttpsCertificateManagerResp, err := d.sdkClient.UpdateHttpsCertificateManager(updateHttpsCertificateManagerReq) + d.logger.Debug("sdk request 'console.EnableDomainHttps'", slog.Any("request", updateHttpsCertificateManagerReq), slog.Any("response", updateHttpsCertificateManagerResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'console.UpdateHttpsCertificateManager': %w", err) + } + } else if getHttpsServiceManagerResp.Data.Domains[lastCertIndex].CertificateId != upres.CertId { + migrateHttpsDomainReq := &upyunsdk.MigrateHttpsDomainRequest{ + CertificateId: upres.CertId, + Domain: d.config.Domain, + } + migrateHttpsDomainResp, err := d.sdkClient.MigrateHttpsDomain(migrateHttpsDomainReq) + d.logger.Debug("sdk request 'console.MigrateHttpsDomain'", slog.Any("request", migrateHttpsDomainReq), slog.Any("response", migrateHttpsDomainResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'console.MigrateHttpsDomain': %w", err) + } + } + + return &core.SSLDeployResult{}, nil +} + +func createSDKClient(username, password string) (*upyunsdk.Client, error) { + return upyunsdk.NewClient(username, password) +} diff --git a/pkg/core/ssl-deployer/providers/upyun-file/upyun_file_test.go b/pkg/core/ssl-deployer/providers/upyun-file/upyun_file_test.go new file mode 100644 index 000000000..f10e0919b --- /dev/null +++ b/pkg/core/ssl-deployer/providers/upyun-file/upyun_file_test.go @@ -0,0 +1,80 @@ +package upyunfile_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/upyun-file" +) + +var ( + fInputCertPath string + fInputKeyPath string + fUsername string + fPassword string + fBucket string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_SSLDEPLOYER_UPYUNFILE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") + flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") + flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./upyun_file_test.go -args \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_USERNAME="your-username" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_PASSWORD="your-password" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_BUCKET="your-bucket" \ + --CERTIMATE_SSLDEPLOYER_UPYUNFILE_DOMAIN="example.com" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + fmt.Sprintf("BUCKET: %v", fBucket), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ + Username: fUsername, + Password: fPassword, + Bucket: fBucket, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuKodo.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuKodo.tsx index 658446146..41e4965b0 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuKodo.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuKodo.tsx @@ -19,6 +19,15 @@ const BizDeployNodeConfigFieldsProviderQiniuKodo = () => { return ( <> + + + + { const getInitialValues = (): Nullish>> => { return { + bucket: "", domain: "", }; }; @@ -41,6 +51,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) const { t } = i18n; return z.object({ + bucket: z.string().nonempty(t("workflow_node.deploy.form.qiniu_kodo_domain.placeholder")), domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), }); }; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderUpyunFile.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderUpyunFile.tsx index bbe5a4f04..99b21823f 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderUpyunFile.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderUpyunFile.tsx @@ -24,6 +24,15 @@ const BizDeployNodeConfigFieldsProviderUpyunFile = () => { } /> + + + + { const getInitialValues = (): Nullish>> => { return { + bucket: "", domain: "", }; }; @@ -46,6 +56,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) const { t } = i18n; return z.object({ + bucket: z.string().nonempty(t("workflow_node.deploy.form.upyun_file_bucket.placeholder")), domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), }); }; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index d99b7da90..38712266b 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -693,6 +693,8 @@ "workflow_node.deploy.form.proxmoxve_auto_restart.label": "Auto restart Proxmox VE after deployment", "workflow_node.deploy.form.qiniu_cdn_domain.label": "Qiniu CDN domain", "workflow_node.deploy.form.qiniu_cdn_domain.placeholder": "Please enter Qiniu CDN domain name", + "workflow_node.deploy.form.qiniu_kodo_bucket.label": "Qiniu Kodo bucket", + "workflow_node.deploy.form.qiniu_kodo_bucket.placeholder": "Please enter Qiniu Kodo bucket name", "workflow_node.deploy.form.qiniu_kodo_domain.label": "Qiniu Kodo custom domain", "workflow_node.deploy.form.qiniu_kodo_domain.placeholder": "Please enter Qiniu Kodo bucket custom domain name", "workflow_node.deploy.form.qiniu_pili_hub.label": "Qiniu Pili hub", @@ -920,6 +922,8 @@ "workflow_node.deploy.form.upyun_cdn_domain.label": "UPYUN CDN domain", "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "Please enter UPYUN CDN domain name", "workflow_node.deploy.form.upyun_file.guide": "This uses webpage simulator login and does not guarantee stability. If there are any changes to the UPYUN, please create a GitHub Issue.", + "workflow_node.deploy.form.upyun_file_bucket.label": "UPYUN USS bucket", + "workflow_node.deploy.form.upyun_file_bucket.placeholder": "Please enter UPYUN USS bucket name", "workflow_node.deploy.form.upyun_file_domain.label": "UPYUN USS custom domain", "workflow_node.deploy.form.upyun_file_domain.placeholder": "Please enter UPYUN USS bucket custom domain name", "workflow_node.deploy.form.volcengine_alb_region.label": "VolcEngine ALB region", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 247796288..4b68a2063 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -691,6 +691,8 @@ "workflow_node.deploy.form.proxmoxve_auto_restart.label": "部署后自动重启 Proxmox VE 服务", "workflow_node.deploy.form.qiniu_cdn_domain.label": "七牛云 CDN 加速域名", "workflow_node.deploy.form.qiniu_cdn_domain.placeholder": "请输入七牛云 CDN 加速域名", + "workflow_node.deploy.form.qiniu_kodo_bucket.label": "七牛云对象存储桶名", + "workflow_node.deploy.form.qiniu_kodo_bucket.placeholder": "请输入七牛云对象存储桶名", "workflow_node.deploy.form.qiniu_kodo_domain.label": "七牛云对象存储自定义域名", "workflow_node.deploy.form.qiniu_kodo_domain.placeholder": "请输入七牛云对象存储自定义域名", "workflow_node.deploy.form.qiniu_pili_hub.label": "七牛云视频直播空间名", @@ -918,6 +920,8 @@ "workflow_node.deploy.form.upyun_cdn_domain.label": "又拍云 CDN 加速域名", "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "请输入又拍云 CDN 加速域名", "workflow_node.deploy.form.upyun_file.guide": "由于又拍云未公开相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇又拍云接口变更,请到 GitHub 发起 Issue 告知。", + "workflow_node.deploy.form.upyun_file_bucket.label": "又拍云云存储桶名", + "workflow_node.deploy.form.upyun_file_bucket.placeholder": "请输入又拍云云存储桶名", "workflow_node.deploy.form.upyun_file_domain.label": "又拍云云存储自定义域名", "workflow_node.deploy.form.upyun_file_domain.placeholder": "请输入又拍云云存储自定义域名", "workflow_node.deploy.form.volcengine_alb_resource_type.label": "证书部署方式", @@ -958,8 +962,8 @@ "workflow_node.deploy.form.volcengine_imagex_region.label": "火山引擎 ImageX 服务地域", "workflow_node.deploy.form.volcengine_imagex_region.placeholder": "请输入火山引擎 ImageX 服务地域(例如:cn-north-1)", "workflow_node.deploy.form.volcengine_imagex_region.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/508/23757", - "workflow_node.deploy.form.volcengine_imagex_service_id.label": "火山引擎 TOS 服务 ID", - "workflow_node.deploy.form.volcengine_imagex_service_id.placeholder": "请输入火山引擎 TOS 服务 ID", + "workflow_node.deploy.form.volcengine_imagex_service_id.label": "火山引擎 ImageX 服务 ID", + "workflow_node.deploy.form.volcengine_imagex_service_id.placeholder": "请输入火山引擎 ImageX 服务 ID", "workflow_node.deploy.form.volcengine_imagex_service_id.tooltip": "这是什么?请参阅 https://console.volcengine.com/imagex", "workflow_node.deploy.form.volcengine_imagex_domain.label": "火山引擎 ImageX 绑定域名", "workflow_node.deploy.form.volcengine_imagex_domain.placeholder": "请输入火山引擎 ImageX 绑定域名", From 0196774907162d805fb193ea191a3f0e07fbd52b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:00:20 +0800 Subject: [PATCH 21/30] feat(provider): supoprt wildcard and certsan match pattern in deployment to aliyun vod --- .../certdeploy/deployers/sp_aliyun_vod.go | 11 +- .../providers/aliyun-live/aliyun_live.go | 4 - .../providers/aliyun-vod/aliyun_vod.go | 146 ++++++++++++++++-- .../providers/aliyun-vod/aliyun_vod_test.go | 9 +- .../providers/aliyun-vod/consts.go | 10 ++ .../providers/aliyun-vod/internal/client.go | 62 ++++++++ ...eployNodeConfigFieldsProviderAliyunVOD.tsx | 71 +++++++-- 7 files changed, 279 insertions(+), 34 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/aliyun-vod/consts.go diff --git a/internal/certdeploy/deployers/sp_aliyun_vod.go b/internal/certdeploy/deployers/sp_aliyun_vod.go index 3c59db40a..f7e23133e 100644 --- a/internal/certdeploy/deployers/sp_aliyun_vod.go +++ b/internal/certdeploy/deployers/sp_aliyun_vod.go @@ -17,11 +17,12 @@ func init() { } provider, err := aliyunvod.NewSSLDeployerProvider(&aliyunvod.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - ResourceGroupId: credentials.ResourceGroupId, - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go index 4386e752f..e0bdb827c 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go +++ b/pkg/core/ssl-deployer/providers/aliyun-live/aliyun_live.go @@ -70,10 +70,6 @@ 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 { diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go index ea99b8012..dbd859738 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go @@ -17,6 +17,8 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/aliyun-vod/internal" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -28,7 +30,10 @@ type SSLDeployerProviderConfig struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` // 阿里云地域。 Region string `json:"region"` - // 点播加速域名(不支持泛域名)。 + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` + // 点播加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -80,26 +85,143 @@ 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("could not find any 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("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 上传证书 - upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) - if err != nil { - return nil, fmt.Errorf("failed to upload certificate file: %w", err) + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no vod domains to deploy") } else { - d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + d.logger.Info("found vod 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://help.aliyun.com/zh/live/developer-reference/api-live-2016-11-01-describeliveuserdomains + describeVodUserDomainsPageNumber := 1 + describeVodUserDomainsPageSize := 50 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeVodUserDomainsReq := &alivod.DescribeVodUserDomainsRequest{ + DomainStatus: tea.String("online"), + PageNumber: tea.Int32(int32(describeVodUserDomainsPageNumber)), + PageSize: tea.Int32(int32(describeVodUserDomainsPageSize)), + } + describeVodUserDomainsResp, err := d.sdkClient.DescribeVodUserDomainsWithContext(ctx, describeVodUserDomainsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'vod.DescribeVodUserDomains'", slog.Any("request", describeVodUserDomainsReq), slog.Any("response", describeVodUserDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'vod.DescribeLiveUserDomains': %w", err) + } + + if describeVodUserDomainsResp.Body == nil || describeVodUserDomainsResp.Body.Domains == nil { + break + } + + for _, domainItem := range describeVodUserDomainsResp.Body.Domains.PageData { + domains = append(domains, tea.StringValue(domainItem.DomainName)) + } + + if len(describeVodUserDomainsResp.Body.Domains.PageData) < describeVodUserDomainsPageSize { + break + } + + describeVodUserDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId, cloudCertName string) error { // 设置域名证书 // REF: https://help.aliyun.com/zh/vod/developer-reference/api-vod-2017-03-21-setvoddomainsslcertificate - certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + certId, _ := strconv.ParseInt(cloudCertId, 10, 64) setVodDomainSSLCertificateReq := &alivod.SetVodDomainSSLCertificateRequest{ DomainName: tea.String(d.config.Domain), CertType: tea.String("cas"), CertId: tea.Int64(certId), - CertName: tea.String(upres.CertName), + CertName: tea.String(cloudCertName), CertRegion: lo. If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")). Else(tea.String("ap-southeast-1")), @@ -108,10 +230,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke setVodDomainSSLCertificateResp, err := d.sdkClient.SetVodDomainSSLCertificateWithContext(ctx, setVodDomainSSLCertificateReq, &dara.RuntimeOptions{}) d.logger.Debug("sdk request 'live.SetVodDomainSSLCertificate'", slog.Any("request", setVodDomainSSLCertificateReq), slog.Any("response", setVodDomainSSLCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'live.SetVodDomainSSLCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'live.SetVodDomainSSLCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret, region string) (*internal.VodClient, error) { diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod_test.go b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod_test.go index 3cf7ec28d..de18f1441 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod_test.go +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Region: fRegion, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/consts.go b/pkg/core/ssl-deployer/providers/aliyun-vod/consts.go new file mode 100644 index 000000000..110fd2a58 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/consts.go @@ -0,0 +1,10 @@ +package aliyunvod + +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/aliyun-vod/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-vod/internal/client.go index a5acc0412..ad23a53af 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-vod/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/internal/client.go @@ -35,6 +35,68 @@ func (client *VodClient) Init(config *openapiutil.Config) (_err error) { return nil } +func (client *VodClient) DescribeVodUserDomainsWithContext(ctx context.Context, request *alivod.DescribeVodUserDomainsRequest, runtime *dara.RuntimeOptions) (_result *alivod.DescribeVodUserDomainsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.DomainName) { + query["DomainName"] = request.DomainName + } + + if !dara.IsNil(request.DomainSearchType) { + query["DomainSearchType"] = request.DomainSearchType + } + + if !dara.IsNil(request.DomainStatus) { + query["DomainStatus"] = request.DomainStatus + } + + if !dara.IsNil(request.OwnerId) { + query["OwnerId"] = request.OwnerId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.SecurityToken) { + query["SecurityToken"] = request.SecurityToken + } + + if !dara.IsNil(request.Tag) { + query["Tag"] = request.Tag + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeVodUserDomains"), + Version: dara.String("2017-03-21"), + Protocol: dara.String("HTTPS"), + Pathname: dara.String("/"), + Method: dara.String("POST"), + AuthType: dara.String("AK"), + Style: dara.String("RPC"), + ReqBodyType: dara.String("formData"), + BodyType: dara.String("json"), + } + _result = &alivod.DescribeVodUserDomainsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *VodClient) SetVodDomainSSLCertificateWithContext(ctx context.Context, request *alivod.SetVodDomainSSLCertificateRequest, runtime *dara.RuntimeOptions) (_result *alivod.SetVodDomainSSLCertificateResponse, _err error) { _err = request.Validate() if _err != nil { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunVOD.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunVOD.tsx index 168bf2785..06e7bd395 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunVOD.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunVOD.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 BizDeployNodeConfigFieldsProviderAliyunVOD = () => { const { i18n, t } = useTranslation(); @@ -15,8 +20,11 @@ const BizDeployNodeConfigFieldsProviderAliyunVOD = () => { [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 ( <> { + ) : ( + void 0 + ) + } rules={[formRule]} > - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -44,6 +76,7 @@ const BizDeployNodeConfigFieldsProviderAliyunVOD = () => { const getInitialValues = (): Nullish>> => { return { region: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -51,10 +84,30 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_vod_region.placeholder")), - domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_vod_region.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderAliyunVOD, { From 5f168ada0f3981be18b4592827d45100ac8fe02d Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:00:21 +0800 Subject: [PATCH 22/30] feat(provider): supoprt certsan match pattern in deployment to qiniu pili --- .../certdeploy/deployers/sp_qiniu_pili.go | 9 +- internal/certdeploy/deployers/sp_upyun_cdn.go | 7 +- .../providers/aliyun-apigw/aliyun_apigw.go | 12 +- .../providers/qiniu-pili/consts.go | 8 ++ .../providers/qiniu-pili/qiniu_pili.go | 109 ++++++++++++++++-- .../providers/qiniu-pili/qiniu_pili_test.go | 7 +- ...eployNodeConfigFieldsProviderQiniuPili.tsx | 62 ++++++++-- 7 files changed, 179 insertions(+), 35 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/qiniu-pili/consts.go diff --git a/internal/certdeploy/deployers/sp_qiniu_pili.go b/internal/certdeploy/deployers/sp_qiniu_pili.go index 06349fcc7..ccf68fd1a 100644 --- a/internal/certdeploy/deployers/sp_qiniu_pili.go +++ b/internal/certdeploy/deployers/sp_qiniu_pili.go @@ -17,10 +17,11 @@ func init() { } provider, err := qiniupili.NewSSLDeployerProvider(&qiniupili.SSLDeployerProviderConfig{ - AccessKey: credentials.AccessKey, - SecretKey: credentials.SecretKey, - Hub: xmaps.GetString(options.ProviderExtendedConfig, "hub"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKey: credentials.AccessKey, + SecretKey: credentials.SecretKey, + Hub: xmaps.GetString(options.ProviderExtendedConfig, "hub"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_upyun_cdn.go b/internal/certdeploy/deployers/sp_upyun_cdn.go index d078dd717..d16751d80 100644 --- a/internal/certdeploy/deployers/sp_upyun_cdn.go +++ b/internal/certdeploy/deployers/sp_upyun_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := upyuncdn.NewSSLDeployerProvider(&upyuncdn.SSLDeployerProviderConfig{ - Username: credentials.Username, - Password: credentials.Password, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + Username: credentials.Username, + Password: credentials.Password, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go index b5e024d7f..84200ab77 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/pkg/core/ssl-deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -141,7 +141,7 @@ func (d *SSLDeployerProvider) deployToTraditional(ctx context.Context, certPEM s } if strings.HasPrefix(d.config.Domain, "*.") { - domainCandidates, err := d.getTraditionalAllDomains(ctx, d.config.GroupId) + domainCandidates, err := d.getTraditionalAllDomainsByGroupId(ctx, d.config.GroupId) if err != nil { return err } @@ -164,7 +164,7 @@ func (d *SSLDeployerProvider) deployToTraditional(ctx context.Context, certPEM s return err } - domainCandidates, err := d.getTraditionalAllDomains(ctx, d.config.GroupId) + domainCandidates, err := d.getTraditionalAllDomainsByGroupId(ctx, d.config.GroupId) if err != nil { return err } @@ -239,7 +239,7 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s } if strings.HasPrefix(d.config.Domain, "*.") { - domainCandidates, err := d.getCloudNativeAllDomains(ctx, d.config.GatewayId) + domainCandidates, err := d.getCloudNativeAllDomainsByGatewayId(ctx, d.config.GatewayId) if err != nil { return err } @@ -262,7 +262,7 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s return err } - domainCandidates, err := d.getCloudNativeAllDomains(ctx, d.config.GatewayId) + domainCandidates, err := d.getCloudNativeAllDomainsByGatewayId(ctx, d.config.GatewayId) if err != nil { return err } @@ -306,7 +306,7 @@ func (d *SSLDeployerProvider) deployToCloudNative(ctx context.Context, certPEM s return nil } -func (d *SSLDeployerProvider) getTraditionalAllDomains(ctx context.Context, cloudGroupId string) ([]string, error) { +func (d *SSLDeployerProvider) getTraditionalAllDomainsByGroupId(ctx context.Context, cloudGroupId string) ([]string, error) { domains := make([]string, 0) // 查询 API 分组详情 @@ -329,7 +329,7 @@ func (d *SSLDeployerProvider) getTraditionalAllDomains(ctx context.Context, clou return domains, nil } -func (d *SSLDeployerProvider) getCloudNativeAllDomains(ctx context.Context, cloudGatewayId string) ([]string, error) { +func (d *SSLDeployerProvider) getCloudNativeAllDomainsByGatewayId(ctx context.Context, cloudGatewayId string) ([]string, error) { domains := make([]string, 0) // 查询域名列表 diff --git a/pkg/core/ssl-deployer/providers/qiniu-pili/consts.go b/pkg/core/ssl-deployer/providers/qiniu-pili/consts.go new file mode 100644 index 000000000..256a9beeb --- /dev/null +++ b/pkg/core/ssl-deployer/providers/qiniu-pili/consts.go @@ -0,0 +1,8 @@ +package qiniupili + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go index bbe2fced1..b089fd44e 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go +++ b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go @@ -7,9 +7,11 @@ import ( "log/slog" "github.com/qiniu/go-sdk/v7/pili" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/qiniu-sslcert" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -19,6 +21,9 @@ type SSLDeployerProviderConfig struct { SecretKey string `json:"secretKey"` // 直播空间名。 Hub string `json:"hub"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 直播流域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -78,18 +83,102 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 修改域名证书配置 - // REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#66 - setDomainCertReq := pili.SetDomainCertRequest{ - Hub: d.config.Hub, - Domain: d.config.Domain, - CertName: upres.CertName, + // 获取待部署的域名列表 + 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_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomainsByHub(ctx, d.config.Hub) + 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("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - err = d.sdkClient.SetDomainCert(ctx, setDomainCertReq) - d.logger.Debug("sdk request 'pili.SetDomainCert'", slog.Any("request", setDomainCertReq)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'pili.SetDomainCert': %w", err) + + // 遍历更新域名证书 + 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, d.config.Hub, domain, upres.CertName); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } } return &core.SSLDeployResult{}, nil } + +func (d *SSLDeployerProvider) getAllDomainsByHub(ctx context.Context, hub string) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#6 + getDomainListReq := pili.GetDomainsListRequest{ + Hub: hub, + } + getDomainListResp, err := d.sdkClient.GetDomainsList(ctx, getDomainListReq) + d.logger.Debug("sdk request 'pili.GetDomainsList'", slog.Any("request", getDomainListReq), slog.Any("response", getDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'pili.GetDomainsList': %w", err) + } + + for _, domainItem := range getDomainListResp.Domains { + domains = append(domains, domainItem.Domain) + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, hub string, domain string, cloudCertName string) error { + // 修改域名证书配置 + // REF: https://developer.qiniu.com/pili/9910/pili-service-sdk#6 + setDomainCertReq := pili.SetDomainCertRequest{ + Hub: hub, + Domain: domain, + CertName: cloudCertName, + } + err := d.sdkClient.SetDomainCert(ctx, setDomainCertReq) + d.logger.Debug("sdk request 'pili.SetDomainCert'", slog.Any("request", setDomainCertReq)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'pili.SetDomainCert': %w", err) + } + + return nil +} diff --git a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili_test.go b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili_test.go index 5ab99fd97..df178eb5e 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili_test.go +++ b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili_test.go @@ -57,9 +57,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKey: fAccessKey, - SecretKey: fSecretKey, - Domain: fDomain, + AccessKey: fAccessKey, + SecretKey: fSecretKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuPili.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuPili.tsx index b63652825..78e26f09b 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuPili.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuPili.tsx @@ -1,12 +1,16 @@ 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_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderQiniuPili = () => { const { i18n, t } = useTranslation(); @@ -15,8 +19,11 @@ const BizDeployNodeConfigFieldsProviderQiniuPili = () => { [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 ( <> { - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -44,6 +68,7 @@ const BizDeployNodeConfigFieldsProviderQiniuPili = () => { const getInitialValues = (): Nullish>> => { return { hub: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -51,10 +76,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - hub: z.string().nonempty(t("workflow_node.deploy.form.qiniu_pili_hub.placeholder")), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + hub: z.string().nonempty(t("workflow_node.deploy.form.qiniu_pili_hub.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderQiniuPili, { From 43c4d13b24c0dc3e2ee0f4b4fe0d1189e7402fae Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:00:21 +0800 Subject: [PATCH 23/30] feat(provider): supoprt certsan match pattern in deployment to jdcloud live --- .../certdeploy/deployers/sp_jdcloud_live.go | 7 +- .../providers/aliyun-vod/aliyun_vod.go | 2 +- .../providers/ctcccloud-ao/ctcccloud_ao.go | 4 +- .../providers/ctcccloud-cdn/ctcccloud_cdn.go | 4 +- .../ctcccloud-icdn/ctcccloud_icdn.go | 4 +- .../providers/jdcloud-live/consts.go | 8 ++ .../providers/jdcloud-live/internal/client.go | 19 +++ .../providers/jdcloud-live/jdcloud_live.go | 118 +++++++++++++++++- .../jdcloud-live/jdcloud_live_test.go | 7 +- ...loyNodeConfigFieldsProviderJDCloudLive.tsx | 60 +++++++-- 10 files changed, 207 insertions(+), 26 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/jdcloud-live/consts.go diff --git a/internal/certdeploy/deployers/sp_jdcloud_live.go b/internal/certdeploy/deployers/sp_jdcloud_live.go index 141f26fcd..7cb5df754 100644 --- a/internal/certdeploy/deployers/sp_jdcloud_live.go +++ b/internal/certdeploy/deployers/sp_jdcloud_live.go @@ -17,9 +17,10 @@ func init() { } provider, err := jdcloudlive.NewSSLDeployerProvider(&jdcloudlive.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go index dbd859738..62e66bab0 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go @@ -218,7 +218,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai // REF: https://help.aliyun.com/zh/vod/developer-reference/api-vod-2017-03-21-setvoddomainsslcertificate certId, _ := strconv.ParseInt(cloudCertId, 10, 64) setVodDomainSSLCertificateReq := &alivod.SetVodDomainSSLCertificateRequest{ - DomainName: tea.String(d.config.Domain), + DomainName: tea.String(domain), CertType: tea.String("cas"), CertId: tea.Int64(certId), CertName: tea.String(cloudCertName), diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go index dfe672fc2..91b5fe11d 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-ao/ctcccloud_ao.go @@ -152,7 +152,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.updateDomainCertificate(ctx, domain, upres.CertId, upres.CertName); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertName); err != nil { errs = append(errs, err) } } @@ -214,7 +214,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return domains, nil } -func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId, cloudCertName string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertName string) error { // 域名基础及加速配置查询 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13412&data=174&isNormal=1&vid=167 getDomainConfigReq := &ctyunao.GetDomainConfigRequest{ diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go index e4603ba4b..dbf90e70e 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-cdn/ctcccloud_cdn.go @@ -151,7 +151,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.updateDomainCertificate(ctx, domain, upres.CertId, upres.CertName); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertName); err != nil { errs = append(errs, err) } } @@ -217,7 +217,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return domains, nil } -func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId, cloudCertName string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertName string) error { // 查询域名配置信息 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11304&data=161&isNormal=1&vid=154 queryDomainDetailReq := &ctyuncdn.QueryDomainDetailRequest{ diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go index 0760693e1..3b2107b3b 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-icdn/ctcccloud_icdn.go @@ -151,7 +151,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.updateDomainCertificate(ctx, domain, upres.CertId, upres.CertName); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertName); err != nil { errs = append(errs, err) } } @@ -213,7 +213,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro return domains, nil } -func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId, cloudCertName string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertName string) error { // 查询域名配置信息 // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10849&data=173&isNormal=1&vid=166 queryDomainDetailReq := &ctyunicdn.QueryDomainDetailRequest{ diff --git a/pkg/core/ssl-deployer/providers/jdcloud-live/consts.go b/pkg/core/ssl-deployer/providers/jdcloud-live/consts.go new file mode 100644 index 000000000..854b44fab --- /dev/null +++ b/pkg/core/ssl-deployer/providers/jdcloud-live/consts.go @@ -0,0 +1,8 @@ +package jdcloudlive + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-live/internal/client.go b/pkg/core/ssl-deployer/providers/jdcloud-live/internal/client.go index bdbc6044c..76479d1bf 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-live/internal/client.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-live/internal/client.go @@ -33,6 +33,25 @@ func NewLiveClient(credential *core.Credential) *LiveClient { } } +func (c *LiveClient) DescribeLiveDomains(request *live.DescribeLiveDomainsRequest) (*live.DescribeLiveDomainsResponse, error) { + if request == nil { + return nil, errors.New("Request object is nil.") + } + resp, err := c.Send(request, c.ServiceName) + if err != nil { + return nil, err + } + + jdResp := &live.DescribeLiveDomainsResponse{} + err = json.Unmarshal(resp, jdResp) + if err != nil { + c.Logger.Log(core.LogError, "Unmarshal json failed, resp: %s", string(resp)) + return nil, err + } + + return jdResp, err +} + func (c *LiveClient) SetLiveDomainCertificate(request *live.SetLiveDomainCertificateRequest) (*live.SetLiveDomainCertificateResponse, error) { if request == nil { return nil, errors.New("Request object is nil.") diff --git a/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live.go b/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live.go index ae2da9acc..2dcaa0c83 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live.go @@ -9,8 +9,10 @@ import ( "github.com/certimate-go/certimate/pkg/core" jdcore "github.com/jdcloud-api/jdcloud-sdk-go/core" jdlive "github.com/jdcloud-api/jdcloud-sdk-go/services/live/apis" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/jdcloud-live/internal" + 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"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 直播播放域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -56,24 +61,127 @@ 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, fmt.Errorf("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_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("could not find any 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 live domains to deploy") + } else { + d.logger.Info("found live 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://docs.jdcloud.com/cn/live-video/api/describelivedomains + describeLiveDomainsPageNumber := 1 + describeLiveDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeLiveDomainsReq := jdlive.NewDescribeLiveDomainsRequestWithoutParam() + describeLiveDomainsReq.SetPageNum(describeLiveDomainsPageNumber) + describeLiveDomainsReq.SetPageSize(describeLiveDomainsPageSize) + describeLiveDomainsResp, err := d.sdkClient.DescribeLiveDomains(describeLiveDomainsReq) + d.logger.Debug("sdk request 'live.DescribeLiveDomainsRequest'", slog.Any("request", describeLiveDomainsReq), slog.Any("response", describeLiveDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'live.DescribeLiveDomainsRequest': %w", err) + } + + ignoredStatuses := []string{"offline", "checking", "check_failed"} + for _, domainItem := range describeLiveDomainsResp.Result.DomainDetails { + for _, playDomainItem := range domainItem.PlayDomains { + if lo.Contains(ignoredStatuses, playDomainItem.DomainStatus) { + continue + } + + domains = append(domains, playDomainItem.PlayDomain) + } + } + + if len(describeLiveDomainsResp.Result.DomainDetails) < describeLiveDomainsPageSize { + break + } + + describeLiveDomainsPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 设置直播证书 // REF: https://docs.jdcloud.com/cn/live-video/api/setlivedomaincertificate setLiveDomainCertificateReq := jdlive.NewSetLiveDomainCertificateRequestWithoutParam() - setLiveDomainCertificateReq.SetPlayDomain(d.config.Domain) + setLiveDomainCertificateReq.SetPlayDomain(domain) setLiveDomainCertificateReq.SetCertStatus("on") setLiveDomainCertificateReq.SetCert(certPEM) setLiveDomainCertificateReq.SetKey(privkeyPEM) setLiveDomainCertificateResp, err := d.sdkClient.SetLiveDomainCertificate(setLiveDomainCertificateReq) d.logger.Debug("sdk request 'live.SetLiveDomainCertificate'", slog.Any("request", setLiveDomainCertificateReq), slog.Any("response", setLiveDomainCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'live.SetLiveDomainCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'live.SetLiveDomainCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, accessKeySecret string) (*internal.LiveClient, error) { diff --git a/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live_test.go b/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live_test.go index 4853dde5e..fa7179e32 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live_test.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-live/jdcloud_live_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudLive.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudLive.tsx index a70897f80..029fa2628 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudLive.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudLive.tsx @@ -1,12 +1,16 @@ 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_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderJDCloudLive = () => { const { i18n, t } = useTranslation(); @@ -15,24 +19,45 @@ const BizDeployNodeConfigFieldsProviderJDCloudLive = () => { [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 ( <> - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +65,28 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!)) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderJDCloudLive, { From d0a62215df2455300dd6b8a46343fc464cbcd853 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:00:21 +0800 Subject: [PATCH 24/30] feat(provider): supoprt certsan match pattern in deployment to jdcloud vod --- .../certdeploy/deployers/sp_jdcloud_vod.go | 7 +- .../providers/jdcloud-vod/consts.go | 8 ++ .../providers/jdcloud-vod/jdcloud_vod.go | 120 +++++++++++++++++- .../providers/jdcloud-vod/jdcloud_vod_test.go | 7 +- ...ployNodeConfigFieldsProviderJDCloudVOD.tsx | 60 +++++++-- 5 files changed, 183 insertions(+), 19 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/jdcloud-vod/consts.go diff --git a/internal/certdeploy/deployers/sp_jdcloud_vod.go b/internal/certdeploy/deployers/sp_jdcloud_vod.go index 0c39162bb..9070e14e0 100644 --- a/internal/certdeploy/deployers/sp_jdcloud_vod.go +++ b/internal/certdeploy/deployers/sp_jdcloud_vod.go @@ -17,9 +17,10 @@ func init() { } provider, err := jdcloudvod.NewSSLDeployerProvider(&jdcloudvod.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - AccessKeySecret: credentials.AccessKeySecret, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + AccessKeySecret: credentials.AccessKeySecret, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/consts.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/consts.go new file mode 100644 index 000000000..2ccb60447 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/consts.go @@ -0,0 +1,8 @@ +package jdcloudvod + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go index 5bb078722..97f4b9ab4 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go @@ -11,8 +11,10 @@ import ( "github.com/certimate-go/certimate/pkg/core" jdcore "github.com/jdcloud-api/jdcloud-sdk-go/core" jdvod "github.com/jdcloud-api/jdcloud-sdk-go/services/vod/apis" + "github.com/samber/lo" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/jdcloud-vod/internal" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -20,6 +22,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 京东云 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 点播加速域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -58,10 +63,115 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { + // 获取待部署的域名列表 + 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_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("could not find any 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 vod domains to deploy") + } else { + d.logger.Info("found vod 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://docs.jdcloud.com/cn/video-on-demand/api/listdomains + listPageNumber := 1 + listDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listDomainsReq := jdvod.NewListDomainsRequestWithoutParam() + listDomainsReq.SetPageNumber(listPageNumber) + listDomainsReq.SetPageSize(listDomainsPageSize) + listDomainsResp, err := d.sdkClient.ListDomains(listDomainsReq) + d.logger.Debug("sdk request 'vod.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'vod.ListDomains': %w", err) + } + + ignoredStatuses := []string{"init", "stopped"} + for _, domainItem := range listDomainsResp.Result.Content { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Name) + } + + if len(listDomainsResp.Result.Content) < listDomainsPageSize { + break + } + + listPageNumber++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 获取域名 ID - domainId, err := d.findDomainIdByDomain(ctx, d.config.Domain) + domainId, err := d.findDomainIdByDomain(ctx, domain) if err != nil { - return nil, err + return err } // 查询域名 SSL 配置 @@ -71,7 +181,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke getHttpSslResp, err := d.sdkClient.GetHttpSsl(getHttpSslReq) d.logger.Debug("sdk request 'vod.GetHttpSsl'", slog.Any("request", getHttpSslReq), slog.Any("response", getHttpSslResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'vod.GetHttpSsl': %w", err) + return fmt.Errorf("failed to execute sdk request 'vod.GetHttpSsl': %w", err) } // 设置域名 SSL 配置 @@ -87,10 +197,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke setHttpSslResp, err := d.sdkClient.SetHttpSsl(setHttpSslReq) d.logger.Debug("sdk request 'vod.SetHttpSsl'", slog.Any("request", setHttpSslReq), slog.Any("response", setHttpSslResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'vod.SetHttpSsl': %w", err) + return fmt.Errorf("failed to execute sdk request 'vod.SetHttpSsl': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func (d *SSLDeployerProvider) findDomainIdByDomain(ctx context.Context, domain string) (int, error) { diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod_test.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod_test.go index c09933202..dd910d7ab 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod_test.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - AccessKeySecret: fAccessKeySecret, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudVOD.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudVOD.tsx index 7577093b5..38e56455c 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudVOD.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderJDCloudVOD.tsx @@ -1,12 +1,16 @@ 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_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderJDCloudVOD = () => { const { i18n, t } = useTranslation(); @@ -15,24 +19,45 @@ const BizDeployNodeConfigFieldsProviderJDCloudVOD = () => { [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 ( <> - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +65,28 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!)) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderJDCloudVOD, { From 4cb51e58e28e0d06cc4c88eb691f85532b2fd2f7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:00:21 +0800 Subject: [PATCH 25/30] feat(provider): supoprt certsan match pattern in deployment to tencentcloud css --- .../deployers/sp_tencentcloud_css.go | 9 +- .../providers/jdcloud-vod/jdcloud_vod.go | 6 +- .../providers/tencentcloud-css/consts.go | 8 ++ .../tencentcloud-css/internal/client.go | 21 ++++ .../tencentcloud-css/tencentcloud_css.go | 99 +++++++++++++++++-- ...odeConfigFieldsProviderTencentCloudCSS.tsx | 62 ++++++++++-- 6 files changed, 179 insertions(+), 26 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/tencentcloud-css/consts.go diff --git a/internal/certdeploy/deployers/sp_tencentcloud_css.go b/internal/certdeploy/deployers/sp_tencentcloud_css.go index bb976ca2d..eb728d707 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_css.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_css.go @@ -17,10 +17,11 @@ func init() { } provider, err := tencentcloudcss.NewSSLDeployerProvider(&tencentcloudcss.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"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go index 97f4b9ab4..3e2859724 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go @@ -130,7 +130,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro // 查询域名列表 // REF: https://docs.jdcloud.com/cn/video-on-demand/api/listdomains - listPageNumber := 1 + listDomainsPageNumber := 1 listDomainsPageSize := 100 for { select { @@ -140,7 +140,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro } listDomainsReq := jdvod.NewListDomainsRequestWithoutParam() - listDomainsReq.SetPageNumber(listPageNumber) + listDomainsReq.SetPageNumber(listDomainsPageNumber) listDomainsReq.SetPageSize(listDomainsPageSize) listDomainsResp, err := d.sdkClient.ListDomains(listDomainsReq) d.logger.Debug("sdk request 'vod.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp)) @@ -161,7 +161,7 @@ func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, erro break } - listPageNumber++ + listDomainsPageNumber++ } return domains, nil diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-css/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-css/consts.go new file mode 100644 index 000000000..add86a6ac --- /dev/null +++ b/pkg/core/ssl-deployer/providers/tencentcloud-css/consts.go @@ -0,0 +1,8 @@ +package tencentcloudcss + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-css/internal/client.go b/pkg/core/ssl-deployer/providers/tencentcloud-css/internal/client.go index 621dfc23f..1b7aefada 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-css/internal/client.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-css/internal/client.go @@ -23,6 +23,27 @@ func NewLiveClient(credential common.CredentialIface, region string, clientProfi return } +func (c *LiveClient) DescribeLiveDomains(request *tclive.DescribeLiveDomainsRequest) (response *tclive.DescribeLiveDomainsResponse, err error) { + return c.DescribeLiveDomainsWithContext(context.Background(), request) +} + +func (c *LiveClient) DescribeLiveDomainsWithContext(ctx context.Context, request *tclive.DescribeLiveDomainsRequest) (response *tclive.DescribeLiveDomainsResponse, err error) { + if request == nil { + request = tclive.NewDescribeLiveDomainsRequest() + } + c.InitBaseRequest(&request.BaseRequest, "live", tclive.APIVersion, "DescribeLiveDomains") + + if c.GetCredential() == nil { + return nil, errors.New("DescribeLiveDomains require credential") + } + + request.SetContext(ctx) + + response = tclive.NewDescribeLiveDomainsResponse() + err = c.Send(request, response) + return +} + func (c *LiveClient) ModifyLiveDomainCertBindings(request *tclive.ModifyLiveDomainCertBindingsRequest) (response *tclive.ModifyLiveDomainCertBindingsResponse, err error) { return c.ModifyLiveDomainCertBindingsWithContext(context.Background(), request) } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-css/tencentcloud_css.go b/pkg/core/ssl-deployer/providers/tencentcloud-css/tencentcloud_css.go index f29ba15d1..b8602636b 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-css/tencentcloud_css.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-css/tencentcloud_css.go @@ -15,6 +15,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/tencentcloud-css/internal" 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"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 直播播放域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -77,10 +81,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -89,15 +89,51 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 绑定证书对应的播放域名 + // 获取待部署的域名列表 + 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_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("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) + } + + // 批量绑定证书对应的播放域名 // REF: https://cloud.tencent.com/document/api/267/78655 modifyLiveDomainCertBindingsReq := tclive.NewModifyLiveDomainCertBindingsRequest() - modifyLiveDomainCertBindingsReq.DomainInfos = []*tclive.LiveCertDomainInfo{ - { - DomainName: common.StringPtr(d.config.Domain), + modifyLiveDomainCertBindingsReq.DomainInfos = lo.Map(domains, func(domain string, _ int) *tclive.LiveCertDomainInfo { + return &tclive.LiveCertDomainInfo{ + DomainName: common.StringPtr(domain), Status: common.Int64Ptr(1), - }, - } + } + }) modifyLiveDomainCertBindingsReq.CloudCertId = common.StringPtr(upres.CertId) modifyLiveDomainCertBindingsResp, err := d.sdkClient.ModifyLiveDomainCertBindings(modifyLiveDomainCertBindingsReq) d.logger.Debug("sdk request 'live.ModifyLiveDomainCertBindings'", slog.Any("request", modifyLiveDomainCertBindingsReq), slog.Any("response", modifyLiveDomainCertBindingsResp)) @@ -108,6 +144,49 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 查询域名列表 + // REF: https://cloud.tencent.com/document/api/267/33856 + describeLiveDomainsPageNum := 1 + describeLiveDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeLiveDomainsReq := tclive.NewDescribeLiveDomainsRequest() + describeLiveDomainsReq.DomainStatus = common.Uint64Ptr(1) + describeLiveDomainsReq.DomainType = common.Uint64Ptr(1) + describeLiveDomainsReq.PageNum = common.Uint64Ptr(uint64(describeLiveDomainsPageNum)) + describeLiveDomainsReq.PageSize = common.Uint64Ptr(uint64(describeLiveDomainsPageSize)) + describeLiveDomainsResp, err := d.sdkClient.DescribeLiveDomains(describeLiveDomainsReq) + d.logger.Debug("sdk request 'live.DescribeLiveDomains'", slog.Any("request", describeLiveDomainsReq), slog.Any("response", describeLiveDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'live.DescribeLiveDomains': %w", err) + } + + if describeLiveDomainsResp.Response == nil { + break + } + + for _, domainItem := range describeLiveDomainsResp.Response.DomainList { + domains = append(domains, *domainItem.Name) + } + + if len(describeLiveDomainsResp.Response.DomainList) < describeLiveDomainsPageSize { + break + } + + describeLiveDomainsPageNum++ + } + + return domains, nil +} + func createSDKClient(secretId, secretKey, endpoint string) (*internal.LiveClient, error) { credential := common.NewCredential(secretId, secretKey) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCSS.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCSS.tsx index b0559d7b3..d6793eb49 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCSS.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudCSS.tsx @@ -1,12 +1,16 @@ 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_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderTencentCloudCSS = () => { const { i18n, t } = useTranslation(); @@ -15,8 +19,11 @@ const BizDeployNodeConfigFieldsProviderTencentCloudCSS = () => { [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 ( <> { - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -50,10 +75,29 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - endpoint: z.string().nullish(), - domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + endpoint: z.string().nullish(), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!)) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderTencentCloudCSS, { From ca24889ebeaf803394d54594f8213d3171235d2b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:00:21 +0800 Subject: [PATCH 26/30] feat(provider): supoprt certsan match pattern in deployment to tencentcloud vod --- .../deployers/sp_tencentcloud_vod.go | 11 +- .../tencentcloud-ecdn/tencentcloud_ecdn.go | 4 +- .../providers/tencentcloud-vod/consts.go | 8 ++ .../tencentcloud-vod/internal/client.go | 21 ++++ .../tencentcloud-vod/tencentcloud_vod.go | 117 ++++++++++++++++-- .../tencentcloud-vod/tencentcloud_vod_test.go | 9 +- ...odeConfigFieldsProviderTencentCloudVOD.tsx | 76 +++++++++--- 7 files changed, 211 insertions(+), 35 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/tencentcloud-vod/consts.go diff --git a/internal/certdeploy/deployers/sp_tencentcloud_vod.go b/internal/certdeploy/deployers/sp_tencentcloud_vod.go index 169c585f2..975de9157 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_vod.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_vod.go @@ -17,11 +17,12 @@ func init() { } provider, err := tencentcloudvod.NewSSLDeployerProvider(&tencentcloudvod.SSLDeployerProviderConfig{ - SecretId: credentials.SecretId, - SecretKey: credentials.SecretKey, - Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), - SubAppId: xmaps.GetInt64(options.ProviderExtendedConfig, "subAppId"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + SubAppId: xmaps.GetInt64(options.ProviderExtendedConfig, "subAppId"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) 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 ff3781a96..c8a2cce64 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -145,7 +145,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - if err := d.updateDomainHttpsServerCert(ctx, domain, upres.CertId); err != nil { + if err := d.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -231,7 +231,7 @@ func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, clo return domains, nil } -func (d *SSLDeployerProvider) updateDomainHttpsServerCert(ctx context.Context, domain string, cloudCertId string) error { +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 查询域名详细配置 // REF: https://cloud.tencent.com/document/api/228/41117 describeDomainsConfigReq := tccdn.NewDescribeDomainsConfigRequest() diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-vod/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-vod/consts.go new file mode 100644 index 000000000..5714a78ce --- /dev/null +++ b/pkg/core/ssl-deployer/providers/tencentcloud-vod/consts.go @@ -0,0 +1,8 @@ +package tencentcloudvod + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-vod/internal/client.go b/pkg/core/ssl-deployer/providers/tencentcloud-vod/internal/client.go index 578b83fd1..501a841a6 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-vod/internal/client.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-vod/internal/client.go @@ -23,6 +23,27 @@ func NewVodClient(credential common.CredentialIface, region string, clientProfil return } +func (c *VodClient) DescribeVodDomains(request *tcvod.DescribeVodDomainsRequest) (response *tcvod.DescribeVodDomainsResponse, err error) { + return c.DescribeVodDomainsWithContext(context.Background(), request) +} + +func (c *VodClient) DescribeVodDomainsWithContext(ctx context.Context, request *tcvod.DescribeVodDomainsRequest) (response *tcvod.DescribeVodDomainsResponse, err error) { + if request == nil { + request = tcvod.NewDescribeVodDomainsRequest() + } + c.InitBaseRequest(&request.BaseRequest, "vod", tcvod.APIVersion, "DescribeVodDomains") + + if c.GetCredential() == nil { + return nil, errors.New("DescribeVodDomains require credential") + } + + request.SetContext(ctx) + + response = tcvod.NewDescribeVodDomainsResponse() + err = c.Send(request, response) + return +} + func (c *VodClient) SetVodDomainCertificate(request *tcvod.SetVodDomainCertificateRequest) (response *tcvod.SetVodDomainCertificateResponse, err error) { return c.SetVodDomainCertificateWithContext(context.Background(), request) } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod.go b/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod.go index 8fd72d911..8e662e320 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod.go @@ -26,6 +26,9 @@ type SSLDeployerProviderConfig struct { Endpoint string `json:"endpoint,omitempty"` // 点播应用 ID。 SubAppId int64 `json:"subAppId"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 点播加速域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -79,10 +82,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -91,22 +90,124 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的 ECDN 实例 + domains := make([]string, 0) + 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_CERTSAN: + { + domainCandidates, err := d.getAllDomains(ctx) + 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 { + d.logger.Info("no vod domains to deploy") + } else { + d.logger.Info("found vod 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, upres.CertId); 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.tencent.com/document/api/266/54176 + describeVodDomainsOffset := 0 + describeVodDomainsLimit := 20 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeVodDomainsReq := tcvod.NewDescribeVodDomainsRequest() + describeVodDomainsReq.Offset = common.Uint64Ptr(uint64(describeVodDomainsOffset)) + describeVodDomainsReq.Limit = common.Uint64Ptr(uint64(describeVodDomainsLimit)) + if d.config.SubAppId != 0 { + describeVodDomainsReq.SubAppId = common.Uint64Ptr(uint64(d.config.SubAppId)) + } + describeVodDomainsResp, err := d.sdkClient.DescribeVodDomains(describeVodDomainsReq) + d.logger.Debug("sdk request 'vod.DescribeVodDomains'", slog.Any("request", describeVodDomainsReq), slog.Any("response", describeVodDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'vod.DescribeVodDomains': %w", err) + } + + if describeVodDomainsResp.Response == nil { + break + } + + ignoredStatuses := []string{"Locked"} + for _, domainItem := range describeVodDomainsResp.Response.DomainSet { + if lo.Contains(ignoredStatuses, *domainItem.DeployStatus) { + continue + } + + domains = append(domains, *domainItem.Domain) + } + + if len(describeVodDomainsResp.Response.DomainSet) < describeVodDomainsLimit { + break + } + + describeVodDomainsOffset += describeVodDomainsLimit + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { // 设置点播域名 HTTPS 证书 // REF: https://cloud.tencent.com/document/api/266/102015 setVodDomainCertificateReq := tcvod.NewSetVodDomainCertificateRequest() - setVodDomainCertificateReq.Domain = common.StringPtr(d.config.Domain) + setVodDomainCertificateReq.Domain = common.StringPtr(domain) setVodDomainCertificateReq.Operation = common.StringPtr("Set") - setVodDomainCertificateReq.CertID = common.StringPtr(upres.CertId) + setVodDomainCertificateReq.CertID = common.StringPtr(cloudCertId) if d.config.SubAppId != 0 { setVodDomainCertificateReq.SubAppId = common.Uint64Ptr(uint64(d.config.SubAppId)) } setVodDomainCertificateResp, err := d.sdkClient.SetVodDomainCertificate(setVodDomainCertificateReq) d.logger.Debug("sdk request 'vod.SetVodDomainCertificate'", slog.Any("request", setVodDomainCertificateReq), slog.Any("response", setVodDomainCertificateResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'vod.SetVodDomainCertificate': %w", err) + return fmt.Errorf("failed to execute sdk request 'vod.SetVodDomainCertificate': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(secretId, secretKey, endpoint string) (*internal.VodClient, error) { diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go index a15e60403..1b79e9593 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go @@ -59,10 +59,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - SubAppId: fSubAppId, - Domain: fDomain, + SecretId: fSecretId, + SecretKey: fSecretKey, + SubAppId: fSubAppId, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudVOD.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudVOD.tsx index 7ac704cff..6c0216687 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudVOD.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudVOD.tsx @@ -1,12 +1,16 @@ 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_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderTencentCloudVOD = () => { const { i18n, t } = useTranslation(); @@ -15,8 +19,11 @@ const BizDeployNodeConfigFieldsProviderTencentCloudVOD = () => { [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 ( <> { - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -60,17 +85,36 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - endpoint: z.string().nullish(), - subAppId: z - .union([z.string(), z.number().int()]) - .nullish() - .refine((v) => { - if (v == null) return true; - return /^\d+$/.test(v + "") && +v > 0; - }, t("workflow_node.deploy.form.tencentcloud_vod_sub_app_id.placeholder")), - domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + endpoint: z.string().nullish(), + subAppId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (v == null) return true; + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.tencentcloud_vod_sub_app_id.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!)) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderTencentCloudVOD, { From a91b2f0cec481e2c103dfbb475f13fb4852d4e0e Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:00:21 +0800 Subject: [PATCH 27/30] feat(provider): supoprt certsan match pattern in deployment to tencentcloud scf --- .../deployers/sp_tencentcloud_scf.go | 11 +- .../providers/tencentcloud-scf/consts.go | 8 + .../tencentcloud-scf/internal/client.go | 21 +++ .../tencentcloud-scf/tencentcloud_scf.go | 145 ++++++++++++++++-- .../tencentcloud-scf/tencentcloud_scf_test.go | 9 +- ...odeConfigFieldsProviderTencentCloudSCF.tsx | 64 ++++++-- 6 files changed, 222 insertions(+), 36 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/tencentcloud-scf/consts.go diff --git a/internal/certdeploy/deployers/sp_tencentcloud_scf.go b/internal/certdeploy/deployers/sp_tencentcloud_scf.go index 4a85c436c..f28b1dbc9 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_scf.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_scf.go @@ -17,11 +17,12 @@ func init() { } provider, err := tencentcloudscf.NewSSLDeployerProvider(&tencentcloudscf.SSLDeployerProviderConfig{ - SecretId: credentials.SecretId, - SecretKey: credentials.SecretKey, - Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-scf/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-scf/consts.go new file mode 100644 index 000000000..853d0a31f --- /dev/null +++ b/pkg/core/ssl-deployer/providers/tencentcloud-scf/consts.go @@ -0,0 +1,8 @@ +package tencentcloudscf + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal/client.go b/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal/client.go index cf1661896..3f7df8b1e 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal/client.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal/client.go @@ -43,6 +43,27 @@ func (c *ScfClient) GetCustomDomainWithContext(ctx context.Context, request *tcs return } +func (c *ScfClient) ListCustomDomains(request *tcscf.ListCustomDomainsRequest) (response *tcscf.ListCustomDomainsResponse, err error) { + return c.ListCustomDomainsWithContext(context.Background(), request) +} + +func (c *ScfClient) ListCustomDomainsWithContext(ctx context.Context, request *tcscf.ListCustomDomainsRequest) (response *tcscf.ListCustomDomainsResponse, err error) { + if request == nil { + request = tcscf.NewListCustomDomainsRequest() + } + c.InitBaseRequest(&request.BaseRequest, "scf", tcscf.APIVersion, "ListCustomDomains") + + if c.GetCredential() == nil { + return nil, errors.New("ListCustomDomains require credential") + } + + request.SetContext(ctx) + + response = tcscf.NewListCustomDomainsResponse() + err = c.Send(request, response) + return +} + func (c *ScfClient) UpdateCustomDomain(request *tcscf.UpdateCustomDomainRequest) (response *tcscf.UpdateCustomDomainResponse, err error) { return c.UpdateCustomDomainWithContext(context.Background(), request) } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go index 6353ec8e6..8fd1b5087 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go @@ -15,6 +15,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/tencentcloud-scf/internal" 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 { @@ -26,6 +27,9 @@ type SSLDeployerProviderConfig struct { Endpoint string `json:"endpoint,omitempty"` // 腾讯云地域。 Region string `json:"region"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 自定义域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -79,20 +83,6 @@ 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") - } - - // 查看云函数自定义域名详情 - // REF: https://cloud.tencent.com/document/api/583/111924 - getCustomDomainReq := tcscf.NewGetCustomDomainRequest() - getCustomDomainReq.Domain = common.StringPtr(d.config.Domain) - getCustomDomainResp, err := d.sdkClient.GetCustomDomain(getCustomDomainReq) - d.logger.Debug("sdk request 'scf.GetCustomDomain'", slog.Any("request", getCustomDomainReq), slog.Any("response", getCustomDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'scf.GetCustomDomain': %w", err) - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -101,21 +91,142 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的域名列表 + 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_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("could not find any 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 scf domains to deploy") + } else { + d.logger.Info("found scf 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, upres.CertId); 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.tencent.com/document/api/583/111923 + listCustomDomainsOffset := 0 + listCustomDomainsLimit := 20 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeLiveDomainsReq := tcscf.NewListCustomDomainsRequest() + describeLiveDomainsReq.Offset = common.Uint64Ptr(uint64(listCustomDomainsOffset)) + describeLiveDomainsReq.Limit = common.Uint64Ptr(uint64(listCustomDomainsLimit)) + describeLiveDomainsResp, err := d.sdkClient.ListCustomDomains(describeLiveDomainsReq) + d.logger.Debug("sdk request 'scf.DescribeLiveDomains'", slog.Any("request", describeLiveDomainsReq), slog.Any("response", describeLiveDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'scf.DescribeLiveDomains': %w", err) + } + + if describeLiveDomainsResp.Response == nil { + break + } + + for _, domainItem := range describeLiveDomainsResp.Response.Domains { + domains = append(domains, *domainItem.Domain) + } + + if len(describeLiveDomainsResp.Response.Domains) < listCustomDomainsLimit { + break + } + + listCustomDomainsOffset++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 查看云函数自定义域名详情 + // REF: https://cloud.tencent.com/document/api/583/111924 + getCustomDomainReq := tcscf.NewGetCustomDomainRequest() + getCustomDomainReq.Domain = common.StringPtr(d.config.Domain) + getCustomDomainResp, err := d.sdkClient.GetCustomDomain(getCustomDomainReq) + d.logger.Debug("sdk request 'scf.GetCustomDomain'", slog.Any("request", getCustomDomainReq), slog.Any("response", getCustomDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'scf.GetCustomDomain': %w", err) + } else { + if getCustomDomainResp.Response.CertConfig != nil && getCustomDomainResp.Response.CertConfig.CertificateId != nil && *getCustomDomainResp.Response.CertConfig.CertificateId == cloudCertId { + return nil + } + } + // 更新云函数自定义域名 // REF: https://cloud.tencent.com/document/api/583/111922 updateCustomDomainReq := tcscf.NewUpdateCustomDomainRequest() updateCustomDomainReq.Domain = common.StringPtr(d.config.Domain) updateCustomDomainReq.CertConfig = &tcscf.CertConf{ - CertificateId: common.StringPtr(upres.CertId), + CertificateId: common.StringPtr(cloudCertId), } updateCustomDomainReq.Protocol = getCustomDomainResp.Response.Protocol + if updateCustomDomainReq.Protocol == nil || *updateCustomDomainReq.Protocol == "HTTP" { + updateCustomDomainReq.Protocol = common.StringPtr("HTTP&HTTPS") + } updateCustomDomainResp, err := d.sdkClient.UpdateCustomDomain(updateCustomDomainReq) d.logger.Debug("sdk request 'scf.UpdateCustomDomain'", slog.Any("request", updateCustomDomainReq), slog.Any("response", updateCustomDomainResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'scf.UpdateCustomDomain': %w", err) + return fmt.Errorf("failed to execute sdk request 'scf.UpdateCustomDomain': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(secretId, secretKey, endpoint, region string) (*internal.ScfClient, error) { diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go index 0357028d4..de654e373 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go @@ -57,10 +57,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - SecretId: fSecretId, - SecretKey: fSecretKey, - Region: fRegion, - Domain: fDomain, + SecretId: fSecretId, + SecretKey: fSecretKey, + Region: fRegion, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSCF.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSCF.tsx index b9ec5901b..82396383b 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSCF.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSCF.tsx @@ -1,12 +1,16 @@ 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_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderTencentCloudSCF = () => { const { i18n, t } = useTranslation(); @@ -15,8 +19,11 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSCF = () => { [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 ( <> { - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; @@ -54,6 +78,7 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSCF = () => { const getInitialValues = (): Nullish>> => { return { region: "", + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -61,11 +86,30 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - endpoint: z.string().nullish(), - region: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_scf_region.placeholder")), - domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + endpoint: z.string().nullish(), + region: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_scf_region.placeholder")), + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!)) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderTencentCloudSCF, { From fd86570065e341a33c1fdf8380c241a7dc94f5d7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:00:21 +0800 Subject: [PATCH 28/30] feat(provider): supoprt certsan match pattern in deployment to ctyun lvdn --- .../certdeploy/deployers/sp_ctcccloud_lvdn.go | 7 +- .../providers/ctcccloud-lvdn/consts.go | 8 + .../ctcccloud-lvdn/ctcccloud_lvdn.go | 163 +++++++++++++++--- .../ctcccloud-lvdn/ctcccloud_lvdn_test.go | 7 +- .../ctyun/lvdn/api_query_domain_detail.go | 10 +- .../ctyun/lvdn/api_query_domain_list.go | 67 +++++++ pkg/sdk3rd/ctyun/lvdn/types.go | 16 ++ ...yNodeConfigFieldsProviderCTCCCloudLVDN.tsx | 60 ++++++- 8 files changed, 289 insertions(+), 49 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/ctcccloud-lvdn/consts.go create mode 100644 pkg/sdk3rd/ctyun/lvdn/api_query_domain_list.go diff --git a/internal/certdeploy/deployers/sp_ctcccloud_lvdn.go b/internal/certdeploy/deployers/sp_ctcccloud_lvdn.go index 18a088344..b5e720c66 100644 --- a/internal/certdeploy/deployers/sp_ctcccloud_lvdn.go +++ b/internal/certdeploy/deployers/sp_ctcccloud_lvdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := ctcccloudlvdn.NewSSLDeployerProvider(&ctcccloudlvdn.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/ctcccloud-lvdn/consts.go b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/consts.go new file mode 100644 index 000000000..cb9a08634 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/consts.go @@ -0,0 +1,8 @@ +package ctcccloudlvdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go index 971796e0f..d0c3c044e 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go @@ -11,6 +11,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/ctcccloud-lvdn" ctyunlvdn "github.com/certimate-go/certimate/pkg/sdk3rd/ctyun/lvdn" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -18,6 +19,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"` } @@ -66,10 +70,6 @@ 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") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -78,35 +78,146 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 查询域名配置信息 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11473&data=183&isNormal=1&vid=261 - queryDomainDetailReq := &ctyunlvdn.QueryDomainDetailRequest{ - Domain: lo.ToPtr(d.config.Domain), - ProductCode: lo.ToPtr("005"), - } - queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) - d.logger.Debug("sdk request 'lvdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryDomainDetail': %w", err) + // 获取待部署的域名列表 + 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_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("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - // 修改域名配置 - // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11308&data=161&isNormal=1&vid=154 - updateDomainReq := &ctyunlvdn.UpdateDomainRequest{ - Domain: lo.ToPtr(d.config.Domain), - ProductCode: lo.ToPtr("005"), - HttpsSwitch: lo.ToPtr(int32(1)), - CertName: lo.ToPtr(upres.CertName), - } - updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) - d.logger.Debug("sdk request 'lvdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'lvdn.UpdateDomain': %w", err) + // 遍历更新域名证书 + if len(domains) == 0 { + d.logger.Info("no icdn domains to deploy") + } else { + d.logger.Info("found icdn 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, upres.CertName); 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://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11559&data=183&isNormal=1&vid=261 + queryDomainsPage := 1 + queryDomainsPageSize := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + queryDomainListReq := &ctyunlvdn.QueryDomainListRequest{ + Page: lo.ToPtr(int32(queryDomainsPage)), + PageSize: lo.ToPtr(int32(queryDomainsPageSize)), + ProductCode: lo.ToPtr("005"), + } + queryDomainListResp, err := d.sdkClient.QueryDomainList(queryDomainListReq) + d.logger.Debug("sdk request 'cdn.QueryDomainList'", slog.Any("request", queryDomainListReq), slog.Any("response", queryDomainListResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryDomainList': %w", err) + } + + if queryDomainListResp.ReturnObj == nil { + break + } + + ignoredStatuses := []int32{1, 5, 6, 7, 8, 9, 11, 12} + for _, domainItem := range queryDomainListResp.ReturnObj.Results { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Domain) + } + + if len(queryDomainListResp.ReturnObj.Results) < queryDomainsPageSize { + break + } + + queryDomainsPage++ + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertName string) error { + // 查询域名配置信息 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11473&data=183&isNormal=1&vid=261 + queryDomainDetailReq := &ctyunlvdn.QueryDomainDetailRequest{ + Domain: lo.ToPtr(domain), + ProductCode: lo.ToPtr("005"), + } + queryDomainDetailResp, err := d.sdkClient.QueryDomainDetail(queryDomainDetailReq) + d.logger.Debug("sdk request 'lvdn.QueryDomainDetail'", slog.Any("request", queryDomainDetailReq), slog.Any("response", queryDomainDetailResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'lvdn.QueryDomainDetail': %w", err) + } + + // 修改域名配置 + // REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=11308&data=161&isNormal=1&vid=154 + updateDomainReq := &ctyunlvdn.UpdateDomainRequest{ + Domain: lo.ToPtr(domain), + ProductCode: lo.ToPtr("005"), + HttpsSwitch: lo.ToPtr(int32(1)), + CertName: lo.ToPtr(cloudCertName), + } + updateDomainResp, err := d.sdkClient.UpdateDomain(updateDomainReq) + d.logger.Debug("sdk request 'lvdn.UpdateDomain'", slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'lvdn.UpdateDomain': %w", err) + } + + return nil +} + func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunlvdn.Client, error) { return ctyunlvdn.NewClient(accessKeyId, secretAccessKey) } diff --git a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn_test.go b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn_test.go index 0dcab6d5b..34b1eb273 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn_test.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn_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/sdk3rd/ctyun/lvdn/api_query_domain_detail.go b/pkg/sdk3rd/ctyun/lvdn/api_query_domain_detail.go index fde83efb6..188834817 100644 --- a/pkg/sdk3rd/ctyun/lvdn/api_query_domain_detail.go +++ b/pkg/sdk3rd/ctyun/lvdn/api_query_domain_detail.go @@ -13,15 +13,7 @@ type QueryDomainDetailRequest struct { type QueryDomainDetailResponse struct { apiResponseBase - ReturnObj *struct { - Domain string `json:"domain"` - ProductCode string `json:"product_code"` - Status int32 `json:"status"` - AreaScope int32 `json:"area_scope"` - Cname string `json:"cname"` - HttpsSwitch int32 `json:"https_switch"` - CertName string `json:"cert_name"` - } `json:"returnObj,omitempty"` + ReturnObj *DomainDetail `json:"returnObj,omitempty"` } func (c *Client) QueryDomainDetail(req *QueryDomainDetailRequest) (*QueryDomainDetailResponse, error) { diff --git a/pkg/sdk3rd/ctyun/lvdn/api_query_domain_list.go b/pkg/sdk3rd/ctyun/lvdn/api_query_domain_list.go new file mode 100644 index 000000000..14f669b0b --- /dev/null +++ b/pkg/sdk3rd/ctyun/lvdn/api_query_domain_list.go @@ -0,0 +1,67 @@ +package lvdn + +import ( + "context" + "net/http" + "strconv" +) + +type QueryDomainListRequest struct { + Page *int32 `json:"page,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` + Domain *string `json:"domain,omitempty"` + ProductCode *string `json:"product_code,omitempty"` + Status *int32 `json:"status,omitempty"` + AreaScope *int32 `json:"area_scope,omitempty"` +} + +type QueryDomainListResponse struct { + apiResponseBase + + ReturnObj *struct { + Results []*DomainRecord `json:"result,omitempty"` + Page int32 `json:"page,omitempty"` + PageSize int32 `json:"page_size,omitempty"` + PageCount int32 `json:"page_count,omitempty"` + Total int32 `json:"total,omitempty"` + } `json:"returnObj,omitempty"` +} + +func (c *Client) QueryDomainList(req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + return c.QueryDomainListWithContext(context.Background(), req) +} + +func (c *Client) QueryDomainListWithContext(ctx context.Context, req *QueryDomainListRequest) (*QueryDomainListResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/v1/domain/query-domain-list") + if err != nil { + return nil, err + } else { + if req.Page != nil { + httpreq.SetQueryParam("page", strconv.Itoa(int(*req.Page))) + } + if req.PageSize != nil { + httpreq.SetQueryParam("page_size", strconv.Itoa(int(*req.PageSize))) + } + if req.Domain != nil { + httpreq.SetQueryParam("domain", *req.Domain) + } + if req.ProductCode != nil { + httpreq.SetQueryParam("product_code", *req.ProductCode) + } + if req.Status != nil { + httpreq.SetQueryParam("status", strconv.Itoa(int(*req.Status))) + } + if req.AreaScope != nil { + httpreq.SetQueryParam("area_scope", strconv.Itoa(int(*req.AreaScope))) + } + + httpreq.SetContext(ctx) + } + + result := &QueryDomainListResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/ctyun/lvdn/types.go b/pkg/sdk3rd/ctyun/lvdn/types.go index 838fc38f2..b10c4a647 100644 --- a/pkg/sdk3rd/ctyun/lvdn/types.go +++ b/pkg/sdk3rd/ctyun/lvdn/types.go @@ -70,6 +70,22 @@ func (r *apiResponseBase) GetErrorMessage() string { var _ apiResponse = (*apiResponseBase)(nil) +type DomainRecord struct { + Domain string `json:"domain"` + Cname string `json:"cname"` + ProductCode string `json:"product_code"` + ProductName string `json:"product_name"` + AreaScope int32 `json:"area_scope"` + Status int32 `json:"status"` + CreatedTime int64 `json:"insert_date"` +} + +type DomainDetail struct { + DomainRecord + HttpsSwitch int32 `json:"https_switch"` + CertName string `json:"cert_name"` +} + type CertRecord struct { Id int64 `json:"id"` Name string `json:"name"` diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudLVDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudLVDN.tsx index 5e5602c28..a1c397da4 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudLVDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderCTCCCloudLVDN.tsx @@ -1,12 +1,16 @@ 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_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderCTCCCloudLVDN = () => { const { i18n, t } = useTranslation(); @@ -15,24 +19,45 @@ const BizDeployNodeConfigFieldsProviderCTCCCloudLVDN = () => { [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 ( <> - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +65,28 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!)) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderCTCCCloudLVDN, { From 5780f29916294363b20d1b55945d6273a3f17ad6 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:00:21 +0800 Subject: [PATCH 29/30] feat(provider): supoprt certsan match pattern in deployment to dogecloud cdn --- .../certdeploy/deployers/sp_dogecloud_cdn.go | 7 +- .../ctcccloud-lvdn/ctcccloud_lvdn.go | 4 +- .../providers/dogecloud-cdn/consts.go | 8 ++ .../providers/dogecloud-cdn/dogecloud_cdn.go | 118 +++++++++++++++--- .../providers/qiniu-pili/qiniu_pili.go | 4 +- pkg/sdk3rd/dogecloud/api_list_cdn_domain.go | 44 +++++++ ...oyNodeConfigFieldsProviderDogeCloudCDN.tsx | 60 +++++++-- ...eployNodeConfigFieldsProviderQiniuPili.tsx | 2 +- 8 files changed, 217 insertions(+), 30 deletions(-) create mode 100644 pkg/core/ssl-deployer/providers/dogecloud-cdn/consts.go create mode 100644 pkg/sdk3rd/dogecloud/api_list_cdn_domain.go diff --git a/internal/certdeploy/deployers/sp_dogecloud_cdn.go b/internal/certdeploy/deployers/sp_dogecloud_cdn.go index 89dfa5ce4..eb39415f8 100644 --- a/internal/certdeploy/deployers/sp_dogecloud_cdn.go +++ b/internal/certdeploy/deployers/sp_dogecloud_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := pDogeCDN.NewSSLDeployerProvider(&pDogeCDN.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/ctcccloud-lvdn/ctcccloud_lvdn.go b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go index d0c3c044e..31146541b 100644 --- a/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go +++ b/pkg/core/ssl-deployer/providers/ctcccloud-lvdn/ctcccloud_lvdn.go @@ -116,9 +116,9 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 遍历更新域名证书 if len(domains) == 0 { - d.logger.Info("no icdn domains to deploy") + d.logger.Info("no lvdn domains to deploy") } else { - d.logger.Info("found icdn domains to deploy", slog.Any("domains", domains)) + d.logger.Info("found lvdn domains to deploy", slog.Any("domains", domains)) var errs []error for _, domain := range domains { diff --git a/pkg/core/ssl-deployer/providers/dogecloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/dogecloud-cdn/consts.go new file mode 100644 index 000000000..c468db8d8 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/dogecloud-cdn/consts.go @@ -0,0 +1,8 @@ +package dogecloudcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/dogecloud-cdn/dogecloud_cdn.go b/pkg/core/ssl-deployer/providers/dogecloud-cdn/dogecloud_cdn.go index 4681f2a35..e756710c9 100644 --- a/pkg/core/ssl-deployer/providers/dogecloud-cdn/dogecloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/dogecloud-cdn/dogecloud_cdn.go @@ -7,9 +7,12 @@ import ( "log/slog" "strconv" + "github.com/samber/lo" + "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/dogecloud" dogesdk "github.com/certimate-go/certimate/pkg/sdk3rd/dogecloud" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -17,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKey string `json:"accessKey"` // 多吉云 SecretKey。 SecretKey string `json:"secretKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(不支持泛域名)。 Domain string `json:"domain"` } @@ -67,10 +73,6 @@ 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, fmt.Errorf("config `domain` is required") - } - // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -79,22 +81,110 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 绑定证书 - // REF: https://docs.dogecloud.com/cdn/api-cert-bind - bindCdnCertId, _ := strconv.ParseInt(upres.CertId, 10, 64) - bindCdnCertReq := &dogesdk.BindCdnCertRequest{ - CertId: bindCdnCertId, - Domain: d.config.Domain, + // 获取待部署的域名列表 + 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_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("could not find any domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - bindCdnCertResp, err := d.sdkClient.BindCdnCert(bindCdnCertReq) - d.logger.Debug("sdk request 'cdn.BindCdnCert'", slog.Any("request", bindCdnCertReq), slog.Any("response", bindCdnCertResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.BindCdnCert': %w", err) + + // 遍历更新域名证书 + 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: + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + if err := d.updateDomainCertificate(ctx, domain, certId); 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://docs.dogecloud.com/cdn/api-domain-list + listCdnDomainResp, err := d.sdkClient.ListCdnDomain() + d.logger.Debug("sdk request 'cdn.ListCdnDomain'", slog.Any("response", listCdnDomainResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCdnDomain': %w", err) + } + + if listCdnDomainResp.Data != nil { + ignoredStatuses := []string{"offline"} + for _, domainItem := range listCdnDomainResp.Data.Domains { + if lo.Contains(ignoredStatuses, domainItem.Status) { + continue + } + + domains = append(domains, domainItem.Name) + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId int64) error { + // 绑定证书 + // REF: https://docs.dogecloud.com/cdn/api-cert-bind + bindCdnCertReq := &dogesdk.BindCdnCertRequest{ + CertId: cloudCertId, + Domain: domain, + } + bindCdnCertResp, err := d.sdkClient.BindCdnCert(bindCdnCertReq) + d.logger.Debug("sdk request 'cdn.BindCdnCert'", slog.Any("request", bindCdnCertReq), slog.Any("response", bindCdnCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'cdn.BindCdnCert': %w", err) + } + + return nil +} + func createSDKClient(accessKey, secretKey string) (*dogesdk.Client, error) { return dogesdk.NewClient(accessKey, secretKey) } diff --git a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go index b089fd44e..684321324 100644 --- a/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go +++ b/pkg/core/ssl-deployer/providers/qiniu-pili/qiniu_pili.go @@ -121,9 +121,9 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 遍历更新域名证书 if len(domains) == 0 { - d.logger.Info("no cdn domains to deploy") + d.logger.Info("no pili domains to deploy") } else { - d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) + d.logger.Info("found pili domains to deploy", slog.Any("domains", domains)) var errs []error for _, domain := range domains { diff --git a/pkg/sdk3rd/dogecloud/api_list_cdn_domain.go b/pkg/sdk3rd/dogecloud/api_list_cdn_domain.go new file mode 100644 index 000000000..070a5f37a --- /dev/null +++ b/pkg/sdk3rd/dogecloud/api_list_cdn_domain.go @@ -0,0 +1,44 @@ +package dogecloud + +import ( + "context" + "encoding/json" + "net/http" +) + +type ListCdnDomainResponse struct { + apiResponseBase + + Data *struct { + Domains []*struct { + Id int64 `json:"id"` + Name string `json:"name"` + Cname string `json:"cname"` + ServiceType string `json:"service_type"` + Status string `json:"status"` + Source json.RawMessage `json:"source"` + CreateTime string `json:"ctime"` + CertId int64 `json:"cert_id"` + } `json:"domains"` + } `json:"data,omitempty"` +} + +func (c *Client) ListCdnDomain() (*ListCdnDomainResponse, error) { + return c.ListCdnDomainWithContext(context.Background()) +} + +func (c *Client) ListCdnDomainWithContext(ctx context.Context) (*ListCdnDomainResponse, error) { + httpreq, err := c.newRequest(http.MethodGet, "/cdn/domain/list.json") + if err != nil { + return nil, err + } else { + httpreq.SetContext(ctx) + } + + result := &ListCdnDomainResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderDogeCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderDogeCloudCDN.tsx index 00ecd8c35..f746100da 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderDogeCloudCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderDogeCloudCDN.tsx @@ -1,12 +1,16 @@ 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_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderDogeCloudCDN = () => { const { i18n, t } = useTranslation(); @@ -15,24 +19,45 @@ const BizDeployNodeConfigFieldsProviderDogeCloudCDN = () => { [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 ( <> - + ({ + key: s, + label: t(`workflow_node.deploy.form.shared_domain_match_pattern.option.${s}.label`), + value: s, + }))} + /> + + + + + + ); }; const getInitialValues = (): Nullish>> => { return { + domainMatchPattern: DOMAIN_MATCH_PATTERN_EXACT, domain: "", }; }; @@ -40,9 +65,28 @@ const getInitialValues = (): Nullish>> => { const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), - }); + return z + .object({ + domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.domainMatchPattern) { + switch (values.domainMatchPattern) { + case DOMAIN_MATCH_PATTERN_EXACT: + { + if (!validDomainName(values.domain!)) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + } + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderDogeCloudCDN, { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuPili.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuPili.tsx index 78e26f09b..54cb248dd 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuPili.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderQiniuPili.tsx @@ -87,7 +87,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) switch (values.domainMatchPattern) { case DOMAIN_MATCH_PATTERN_EXACT: { - if (!validDomainName(values.domain!, { allowWildcard: true })) { + if (!validDomainName(values.domain!)) { ctx.addIssue({ code: "custom", message: t("common.errmsg.domain_invalid"), From 977aa1830f47dd2b88ae95cd6ab3976ae7795f78 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 13 Nov 2025 23:08:03 +0800 Subject: [PATCH 30/30] fix: typo --- pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go | 2 +- .../providers/tencentcloud-scf/tencentcloud_scf.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go index 3e2859724..c1462c685 100644 --- a/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go +++ b/pkg/core/ssl-deployer/providers/jdcloud-vod/jdcloud_vod.go @@ -225,7 +225,7 @@ func (d *SSLDeployerProvider) findDomainIdByDomain(ctx context.Context, domain s } for _, domainItem := range listDomainsResp.Result.Content { - if domainItem.Name == d.config.Domain { + if domainItem.Name == domain { domainId, _ := strconv.Atoi(domainItem.Id) return domainId, nil } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go index 8fd1b5087..701aca45e 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-scf/tencentcloud_scf.go @@ -198,7 +198,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai // 查看云函数自定义域名详情 // REF: https://cloud.tencent.com/document/api/583/111924 getCustomDomainReq := tcscf.NewGetCustomDomainRequest() - getCustomDomainReq.Domain = common.StringPtr(d.config.Domain) + getCustomDomainReq.Domain = common.StringPtr(domain) getCustomDomainResp, err := d.sdkClient.GetCustomDomain(getCustomDomainReq) d.logger.Debug("sdk request 'scf.GetCustomDomain'", slog.Any("request", getCustomDomainReq), slog.Any("response", getCustomDomainResp)) if err != nil { @@ -212,7 +212,7 @@ func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domai // 更新云函数自定义域名 // REF: https://cloud.tencent.com/document/api/583/111922 updateCustomDomainReq := tcscf.NewUpdateCustomDomainRequest() - updateCustomDomainReq.Domain = common.StringPtr(d.config.Domain) + updateCustomDomainReq.Domain = common.StringPtr(domain) updateCustomDomainReq.CertConfig = &tcscf.CertConf{ CertificateId: common.StringPtr(cloudCertId), }