diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 56427064..7abd7185 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -59,6 +59,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, ResourceType: providerAliyunALB.DeployResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")), LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"), ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -77,7 +78,8 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), ResourceType: providerAliyunCLB.DeployResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")), LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerPort: maps.GetValueAsInt32(options.ProviderDeployConfig, "listenerPort"), + ListenerPort: maps.GetValueOrDefaultAsInt32(options.ProviderDeployConfig, "listenerPort", 443), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), }, logger) return deployer, logger, err @@ -251,10 +253,9 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } - sshPort := access.Port deployer, err := providerSSH.NewWithLogger(&providerSSH.SshDeployerConfig{ SshHost: access.Host, - SshPort: int32(sshPort), + SshPort: access.Port, SshUsername: access.Username, SshPassword: access.Password, SshKey: access.Key, diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go index a92b4d33..355f4c0b 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -4,12 +4,16 @@ import ( "context" "errors" "fmt" + "strconv" "strings" + "time" aliyunAlb "github.com/alibabacloud-go/alb-20200616/v2/client" + aliyunCas "github.com/alibabacloud-go/cas-20200407/v3/client" aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" "github.com/alibabacloud-go/tea/tea" xerrors "github.com/pkg/errors" + "golang.org/x/exp/slices" "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" @@ -32,17 +36,25 @@ type AliyunALBDeployerConfig struct { // 负载均衡监听 ID。 // 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。 ListenerId string `json:"listenerId,omitempty"` + // SNI 域名(支持泛域名)。 + // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER] 时选填。 + Domain string `json:"domain,omitempty"` } type AliyunALBDeployer struct { config *AliyunALBDeployerConfig logger logger.Logger - sdkClient *aliyunAlb.Client + sdkClients *wSdkClients sslUploader uploader.Uploader } var _ deployer.Deployer = (*AliyunALBDeployer)(nil) +type wSdkClients struct { + alb *aliyunAlb.Client + cas *aliyunCas.Client +} + func New(config *AliyunALBDeployerConfig) (*AliyunALBDeployer, error) { return NewWithLogger(config, logger.NewNilLogger()) } @@ -56,9 +68,9 @@ func NewWithLogger(config *AliyunALBDeployerConfig, logger logger.Logger) (*Aliy return nil, errors.New("logger is nil") } - client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region) + clients, err := createSdkClients(config.AccessKeyId, config.AccessKeySecret, config.Region) if err != nil { - return nil, xerrors.Wrap(err, "failed to create sdk client") + return nil, xerrors.Wrap(err, "failed to create sdk clients") } aliyunCasRegion := config.Region @@ -84,7 +96,7 @@ func NewWithLogger(config *AliyunALBDeployerConfig, logger logger.Logger) (*Aliy return &AliyunALBDeployer{ logger: logger, config: config, - sdkClient: client, + sdkClients: clients, sslUploader: uploader, }, nil } @@ -122,14 +134,12 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI return errors.New("config `loadbalancerId` is required") } - listenerIds := make([]string, 0) - // 查询负载均衡实例的详细信息 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getloadbalancerattribute getLoadBalancerAttributeReq := &aliyunAlb.GetLoadBalancerAttributeRequest{ LoadBalancerId: tea.String(d.config.LoadbalancerId), } - getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq) + getLoadBalancerAttributeResp, err := d.sdkClients.alb.GetLoadBalancerAttribute(getLoadBalancerAttributeReq) if err != nil { return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetLoadBalancerAttribute'") } @@ -138,7 +148,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners - listListenersPage := 1 + listenerIds := make([]string, 0) listListenersLimit := int32(100) var listListenersToken *string = nil for { @@ -148,7 +158,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, ListenerProtocol: tea.String("HTTPS"), } - listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + listListenersResp, err := d.sdkClients.alb.ListListeners(listListenersReq) if err != nil { return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'") } @@ -163,7 +173,6 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI break } else { listListenersToken = listListenersResp.Body.NextToken - listListenersPage += 1 } } @@ -171,7 +180,6 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI // 查询 QUIC 监听列表 // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners - listListenersPage = 1 listListenersToken = nil for { listListenersReq := &aliyunAlb.ListListenersRequest{ @@ -180,7 +188,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, ListenerProtocol: tea.String("QUIC"), } - listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + listListenersResp, err := d.sdkClients.alb.ListListeners(listListenersReq) if err != nil { return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'") } @@ -195,21 +203,26 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI break } else { listListenersToken = listListenersResp.Body.NextToken - listListenersPage += 1 } } d.logger.Logt("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", listenerIds) - // 批量更新监听证书 - var errs []error - for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + // 遍历更新监听证书 + if len(listenerIds) == 0 { + return xerrors.New("listener not found") + } else { + var errs []error + + for _, listenerId := range listenerIds { + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return errors.Join(errs...) } - } - if len(errs) > 0 { - return errors.Join(errs...) } return nil @@ -234,57 +247,202 @@ func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, cloud getListenerAttributeReq := &aliyunAlb.GetListenerAttributeRequest{ ListenerId: tea.String(cloudListenerId), } - getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq) + getListenerAttributeResp, err := d.sdkClients.alb.GetListenerAttribute(getListenerAttributeReq) if err != nil { return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetListenerAttribute'") } d.logger.Logt("已查询到 ALB 监听配置", getListenerAttributeResp) - // 修改监听的属性 - // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-updatelistenerattribute - updateListenerAttributeReq := &aliyunAlb.UpdateListenerAttributeRequest{ - ListenerId: tea.String(cloudListenerId), - Certificates: []*aliyunAlb.UpdateListenerAttributeRequestCertificates{{ - CertificateId: tea.String(cloudCertId), - }}, - } - updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq) - if err != nil { - return xerrors.Wrap(err, "failed to execute sdk request 'alb.UpdateListenerAttribute'") - } + if d.config.Domain == "" { + // 未指定 SNI,只需部署到监听器 - d.logger.Logt("已更新 ALB 监听配置", updateListenerAttributeResp) + // 修改监听的属性 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-updatelistenerattribute + updateListenerAttributeReq := &aliyunAlb.UpdateListenerAttributeRequest{ + ListenerId: tea.String(cloudListenerId), + Certificates: []*aliyunAlb.UpdateListenerAttributeRequestCertificates{{ + CertificateId: tea.String(cloudCertId), + }}, + } + updateListenerAttributeResp, err := d.sdkClients.alb.UpdateListenerAttribute(updateListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.UpdateListenerAttribute'") + } - // TODO: #347 + d.logger.Logt("已更新 ALB 监听配置", updateListenerAttributeResp) + } else { + // 指定 SNI,需部署到扩展域名(支持泛域名) + + // 查询监听证书列表 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlistenercertificates + listenerCertificates := make([]aliyunAlb.ListListenerCertificatesResponseBodyCertificates, 0) + listListenerCertificatesLimit := int32(100) + var listListenerCertificatesToken *string = nil + for { + listListenerCertificatesReq := &aliyunAlb.ListListenerCertificatesRequest{ + NextToken: listListenerCertificatesToken, + MaxResults: tea.Int32(listListenerCertificatesLimit), + ListenerId: tea.String(cloudListenerId), + CertificateType: tea.String("Server"), + } + listListenerCertificatesResp, err := d.sdkClients.alb.ListListenerCertificates(listListenerCertificatesReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListenerCertificates'") + } + + if listListenerCertificatesResp.Body.Certificates != nil { + 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 + } + } + + d.logger.Logt("已查询到 ALB 监听下全部证书", listenerCertificates) + + // 遍历查询监听证书,并找出需要解除关联的证书 + // 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 + certificateIsAssociated := false + certificateIdsExpired := make([]string, 0) + if len(listenerCertificates) > 0 { + var errs []error + + for _, listenerCertificate := range listenerCertificates { + if *listenerCertificate.CertificateId == cloudCertId { + certificateIsAssociated = true + continue + } + + if *listenerCertificate.IsDefault || !strings.EqualFold(*listenerCertificate.Status, "Associated") { + continue + } + + listenerCertificateId, err := strconv.ParseInt(*listenerCertificate.CertificateId, 10, 64) + if err != nil { + errs = append(errs, err) + continue + } + + getUserCertificateDetailReq := &aliyunCas.GetUserCertificateDetailRequest{ + CertId: tea.Int64(listenerCertificateId), + } + getUserCertificateDetailResp, err := d.sdkClients.cas.GetUserCertificateDetail(getUserCertificateDetailReq) + if err != nil { + errs = append(errs, xerrors.Wrap(err, "failed to execute sdk request 'cas.GetUserCertificateDetail'")) + continue + } + + certCnMatched := getUserCertificateDetailResp.Body.Common != nil && *getUserCertificateDetailResp.Body.Common == d.config.Domain + certSanMatched := getUserCertificateDetailResp.Body.Sans != nil && slices.Contains(strings.Split(*getUserCertificateDetailResp.Body.Sans, ","), d.config.Domain) + if !certCnMatched && !certSanMatched { + continue + } + + certEndDate, _ := time.Parse("2006-01-02", *getUserCertificateDetailResp.Body.EndDate) + if time.Now().Before(certEndDate) { + continue + } + + certificateIdsExpired = append(certificateIdsExpired, *listenerCertificate.CertificateId) + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + } + + // 关联监听和扩展证书 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-associateadditionalcertificateswithlistener + if !certificateIsAssociated { + associateAdditionalCertificatesFromListenerReq := &aliyunAlb.AssociateAdditionalCertificatesWithListenerRequest{ + ListenerId: tea.String(cloudListenerId), + Certificates: []*aliyunAlb.AssociateAdditionalCertificatesWithListenerRequestCertificates{ + { + CertificateId: tea.String(cloudCertId), + }, + }, + } + associateAdditionalCertificatesFromListenerResp, err := d.sdkClients.alb.AssociateAdditionalCertificatesWithListener(associateAdditionalCertificatesFromListenerReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.AssociateAdditionalCertificatesWithListener'") + } + + d.logger.Logt("已关联 ALB 监听和扩展证书", associateAdditionalCertificatesFromListenerResp) + } + + // 解除关联监听和扩展证书 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-dissociateadditionalcertificatesfromlistener + if len(certificateIdsExpired) > 0 { + dissociateAdditionalCertificates := make([]*aliyunAlb.DissociateAdditionalCertificatesFromListenerRequestCertificates, 0) + for _, certificateId := range certificateIdsExpired { + dissociateAdditionalCertificates = append(dissociateAdditionalCertificates, &aliyunAlb.DissociateAdditionalCertificatesFromListenerRequestCertificates{ + CertificateId: tea.String(certificateId), + }) + } + + dissociateAdditionalCertificatesFromListenerReq := &aliyunAlb.DissociateAdditionalCertificatesFromListenerRequest{ + ListenerId: tea.String(cloudListenerId), + Certificates: dissociateAdditionalCertificates, + } + dissociateAdditionalCertificatesFromListenerResp, err := d.sdkClients.alb.DissociateAdditionalCertificatesFromListener(dissociateAdditionalCertificatesFromListenerReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.DissociateAdditionalCertificatesFromListener'") + } + + d.logger.Logt("已解除关联 ALB 监听和扩展证书", dissociateAdditionalCertificatesFromListenerResp) + } + } return nil } -func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunAlb.Client, error) { - if region == "" { - region = "cn-hangzhou" - } - - // 接入点一览 https://www.alibabacloud.com/help/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-endpoint - var endpoint string +func createSdkClients(accessKeyId, accessKeySecret, region string) (*wSdkClients, error) { + // 接入点一览 https://www.alibabacloud.com/help/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-albEndpoint + var albEndpoint string switch region { case "cn-hangzhou-finance": - endpoint = "alb.cn-hangzhou.aliyuncs.com" + albEndpoint = "alb.cn-hangzhou.aliyuncs.com" default: - endpoint = fmt.Sprintf("alb.%s.aliyuncs.com", region) + albEndpoint = fmt.Sprintf("alb.%s.aliyuncs.com", region) } - config := &aliyunOpen.Config{ + albConfig := &aliyunOpen.Config{ AccessKeyId: tea.String(accessKeyId), AccessKeySecret: tea.String(accessKeySecret), - Endpoint: tea.String(endpoint), + Endpoint: tea.String(albEndpoint), } - - client, err := aliyunAlb.NewClient(config) + albClient, err := aliyunAlb.NewClient(albConfig) if err != nil { return nil, err } - return client, nil + // 接入点一览 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints + var casEndpoint string + if !strings.HasPrefix(region, "cn-") { + casEndpoint = "cas.ap-southeast-1.aliyuncs.com" + } else { + casEndpoint = "cas.aliyuncs.com" + } + + casConfig := &aliyunOpen.Config{ + Endpoint: tea.String(casEndpoint), + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + } + casClient, err := aliyunCas.NewClient(casConfig) + if err != nil { + return nil, err + } + + return &wSdkClients{ + alb: albClient, + cas: casClient, + }, nil } diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go index fddf7ff2..15e6aff2 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go @@ -19,6 +19,7 @@ var ( fRegion string fLoadbalancerId string fListenerId string + fDomain string ) func init() { @@ -31,6 +32,7 @@ func init() { flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") } /* @@ -43,7 +45,8 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNALB_ACCESSKEYSECRET="your-access-key-secret" \ --CERTIMATE_DEPLOYER_ALIYUNALB_REGION="cn-hangzhou" \ --CERTIMATE_DEPLOYER_ALIYUNALB_LOADBALANCERID="your-alb-instance-id" \ - --CERTIMATE_DEPLOYER_ALIYUNALB_LISTENERID="your-alb-listener-id" + --CERTIMATE_DEPLOYER_ALIYUNALB_LISTENERID="your-alb-listener-id" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_DOMAIN="your-alb-sni-domain" */ func TestDeploy(t *testing.T) { flag.Parse() @@ -57,6 +60,7 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), fmt.Sprintf("REGION: %v", fRegion), fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) deployer, err := provider.New(&provider.AliyunALBDeployerConfig{ @@ -65,6 +69,7 @@ func TestDeploy(t *testing.T) { Region: fRegion, ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER, LoadbalancerId: fLoadbalancerId, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) @@ -99,6 +104,7 @@ func TestDeploy(t *testing.T) { Region: fRegion, ResourceType: provider.DEPLOY_RESOURCE_LISTENER, ListenerId: fListenerId, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go index 86f6dfd9..1fe99458 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -31,6 +31,9 @@ type AliyunCLBDeployerConfig struct { // 负载均衡监听端口。 // 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。 ListenerPort int32 `json:"listenerPort,omitempty"` + // SNI 域名(支持泛域名)。 + // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER] 时选填。 + Domain string `json:"domain,omitempty"` } type AliyunCLBDeployer struct { @@ -110,8 +113,6 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI return errors.New("config `loadbalancerId` is required") } - listenerPorts := make([]int32, 0) - // 查询负载均衡实例的详细信息 // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerattribute describeLoadBalancerAttributeReq := &aliyunSlb.DescribeLoadBalancerAttributeRequest{ @@ -127,14 +128,14 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI // 查询 HTTPS 监听列表 // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerlisteners - listListenersPage := 1 - listListenersLimit := int32(100) - var listListenersToken *string = nil + listenerPorts := make([]int32, 0) + describeLoadBalancerListenersLimit := int32(100) + var describeLoadBalancerListenersToken *string = nil for { describeLoadBalancerListenersReq := &aliyunSlb.DescribeLoadBalancerListenersRequest{ RegionId: tea.String(d.config.Region), - MaxResults: tea.Int32(listListenersLimit), - NextToken: listListenersToken, + MaxResults: tea.Int32(describeLoadBalancerListenersLimit), + NextToken: describeLoadBalancerListenersToken, LoadBalancerId: []*string{tea.String(d.config.LoadbalancerId)}, ListenerProtocol: tea.String("https"), } @@ -152,22 +153,27 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI if len(describeLoadBalancerListenersResp.Body.Listeners) == 0 || describeLoadBalancerListenersResp.Body.NextToken == nil { break } else { - listListenersToken = describeLoadBalancerListenersResp.Body.NextToken - listListenersPage += 1 + describeLoadBalancerListenersToken = describeLoadBalancerListenersResp.Body.NextToken } } d.logger.Logt("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", listenerPorts) - // 批量更新监听证书 - var errs []error - for _, listenerPort := range listenerPorts { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { - errs = append(errs, err) + // 遍历更新监听证书 + if len(listenerPorts) == 0 { + return xerrors.New("listener not found") + } else { + var errs []error + + for _, listenerPort := range listenerPorts { + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return errors.Join(errs...) } - } - if len(errs) > 0 { - return errors.Join(errs...) } return nil @@ -203,67 +209,76 @@ func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, cloud d.logger.Logt("已查询到 CLB HTTPS 监听配置", describeLoadBalancerHTTPSListenerAttributeResp) - // 查询扩展域名 - // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describedomainextensions - describeDomainExtensionsReq := &aliyunSlb.DescribeDomainExtensionsRequest{ - RegionId: tea.String(d.config.Region), - LoadBalancerId: tea.String(cloudLoadbalancerId), - ListenerPort: tea.Int32(cloudListenerPort), - } - describeDomainExtensionsResp, err := d.sdkClient.DescribeDomainExtensions(describeDomainExtensionsReq) - if err != nil { - return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeDomainExtensions'") - } + if d.config.Domain == "" { + // 未指定 SNI,只需部署到监听器 - d.logger.Logt("已查询到 CLB 扩展域名", describeDomainExtensionsResp) + // 修改监听配置 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setloadbalancerhttpslistenerattribute + // + // 注意修改监听配置要放在修改扩展域名之后 + setLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.SetLoadBalancerHTTPSListenerAttributeRequest{ + RegionId: tea.String(d.config.Region), + LoadBalancerId: tea.String(cloudLoadbalancerId), + ListenerPort: tea.Int32(cloudListenerPort), + ServerCertificateId: tea.String(cloudCertId), + } + setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.SetLoadBalancerHTTPSListenerAttribute(setLoadBalancerHTTPSListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute'") + } - // 遍历修改扩展域名 - // 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 { - for _, domainExtension := range describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension { - if *domainExtension.ServerCertificateId != *describeLoadBalancerHTTPSListenerAttributeResp.Body.ServerCertificateId { - continue + d.logger.Logt("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp) + } else { + // 指定 SNI,需部署到扩展域名(支持泛域名) + + // 查询扩展域名 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describedomainextensions + describeDomainExtensionsReq := &aliyunSlb.DescribeDomainExtensionsRequest{ + RegionId: tea.String(d.config.Region), + LoadBalancerId: tea.String(cloudLoadbalancerId), + ListenerPort: tea.Int32(cloudListenerPort), + } + describeDomainExtensionsResp, err := d.sdkClient.DescribeDomainExtensions(describeDomainExtensionsReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeDomainExtensions'") + } + + d.logger.Logt("已查询到 CLB 扩展域名", describeDomainExtensionsResp) + + // 遍历修改扩展域名 + // 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 + + for _, domainExtension := range describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension { + if *domainExtension.Domain != d.config.Domain { + continue + } + + setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{ + RegionId: tea.String(d.config.Region), + DomainExtensionId: tea.String(*domainExtension.DomainExtensionId), + ServerCertificateId: tea.String(cloudCertId), + } + setDomainExtensionAttributeResp, err := d.sdkClient.SetDomainExtensionAttribute(setDomainExtensionAttributeReq) + if err != nil { + errs = append(errs, xerrors.Wrap(err, "failed to execute sdk request 'slb.SetDomainExtensionAttribute'")) + continue + } + + d.logger.Logt("已修改 CLB 扩展域名", setDomainExtensionAttributeResp) } - setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{ - RegionId: tea.String(d.config.Region), - DomainExtensionId: tea.String(*domainExtension.DomainExtensionId), - ServerCertificateId: tea.String(cloudCertId), - } - _, err := d.sdkClient.SetDomainExtensionAttribute(setDomainExtensionAttributeReq) - if err != nil { - return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetDomainExtensionAttribute'") + if len(errs) > 0 { + return errors.Join(errs...) } } } - // 修改监听配置 - // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setloadbalancerhttpslistenerattribute - // - // 注意修改监听配置要放在修改扩展域名之后 - setLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.SetLoadBalancerHTTPSListenerAttributeRequest{ - RegionId: tea.String(d.config.Region), - LoadBalancerId: tea.String(cloudLoadbalancerId), - ListenerPort: tea.Int32(cloudListenerPort), - ServerCertificateId: tea.String(cloudCertId), - } - setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.SetLoadBalancerHTTPSListenerAttribute(setLoadBalancerHTTPSListenerAttributeReq) - if err != nil { - return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute'") - } - - d.logger.Logt("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp) - return nil } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) { - if region == "" { - region = "cn-hangzhou" // CLB(SLB) 服务默认区域:华东一杭州 - } - // 接入点一览 https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-endpoint var endpoint string switch region { diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go index 761cbb38..0b27c5f8 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go @@ -19,6 +19,7 @@ var ( fRegion string fLoadbalancerId string fListenerPort int + fDomain string ) func init() { @@ -31,6 +32,7 @@ func init() { flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") flag.IntVar(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") } /* @@ -43,7 +45,8 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNCLB_ACCESSKEYSECRET="your-access-key-secret" \ --CERTIMATE_DEPLOYER_ALIYUNCLB_REGION="cn-hangzhou" \ --CERTIMATE_DEPLOYER_ALIYUNCLB_LOADBALANCERID="your-clb-instance-id" \ - --CERTIMATE_DEPLOYER_ALIYUNCLB_LISTENERPORT=443 + --CERTIMATE_DEPLOYER_ALIYUNCLB_LISTENERPORT=443 \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_DOMAIN="your-alb-sni-domain" */ func TestDeploy(t *testing.T) { flag.Parse() @@ -57,6 +60,7 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), fmt.Sprintf("REGION: %v", fRegion), fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) deployer, err := provider.New(&provider.AliyunCLBDeployerConfig{ @@ -65,6 +69,7 @@ func TestDeploy(t *testing.T) { Region: fRegion, ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER, LoadbalancerId: fLoadbalancerId, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) @@ -101,6 +106,7 @@ func TestDeploy(t *testing.T) { ResourceType: provider.DEPLOY_RESOURCE_LISTENER, LoadbalancerId: fLoadbalancerId, ListenerPort: int32(fListenerPort), + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go index c5558b2c..2b273bc2 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -122,8 +122,6 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI return errors.New("config `loadbalancerId` is required") } - listenerIds := make([]string, 0) - // 查询负载均衡实例的详细信息 // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getloadbalancerattribute getLoadBalancerAttributeReq := &aliyunNlb.GetLoadBalancerAttributeRequest{ @@ -138,7 +136,7 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI // 查询 TCPSSL 监听列表 // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners - listListenersPage := 1 + listenerIds := make([]string, 0) listListenersLimit := int32(100) var listListenersToken *string = nil for { @@ -163,21 +161,26 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI break } else { listListenersToken = listListenersResp.Body.NextToken - listListenersPage += 1 } } d.logger.Logt("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", listenerIds) - // 批量更新监听证书 - var errs []error - for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + // 遍历更新监听证书 + if len(listenerIds) == 0 { + return xerrors.New("listener not found") + } else { + var errs []error + + for _, listenerId := range listenerIds { + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return errors.Join(errs...) } - } - if len(errs) > 0 { - return errors.Join(errs...) } return nil @@ -226,10 +229,6 @@ func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, cloud } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Client, error) { - if region == "" { - region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州 - } - // 接入点一览 https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-endpoint var endpoint string switch region { diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 25424c5f..1afaa219 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -239,7 +239,7 @@ const DeployNodeConfigForm = forwardRef ; export type DeployNodeConfigFormAliyunALBConfigProps = { @@ -56,6 +58,13 @@ const DeployNodeConfigFormAliyunALBConfig = ({ .trim() .nullish() .refine((v) => fieldResourceType !== RESOURCE_TYPE_LISTENER || !!v?.trim(), t("workflow_node.deploy.form.aliyun_alb_listener_id.placeholder")), + domain: z + .string() + .nullish() + .refine((v) => { + if (![RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER].includes(fieldResourceType)) return true; + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), }); const formRule = createSchemaFieldRule(formSchema); @@ -115,6 +124,17 @@ const DeployNodeConfigFormAliyunALBConfig = ({ + + + } + > + + + ); }; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx index be56600b..dfbfc707 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCLBConfig.tsx @@ -4,13 +4,14 @@ import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; import Show from "@/components/Show"; -import { validPortNumber } from "@/utils/validators"; +import { validDomainName, validPortNumber } from "@/utils/validators"; type DeployNodeConfigFormAliyunCLBConfigFieldValues = Nullish<{ resourceType: string; region: string; loadbalancerId?: string; listenerPort?: string | number; + domain?: string; }>; export type DeployNodeConfigFormAliyunCLBConfigProps = { @@ -68,6 +69,13 @@ const DeployNodeConfigFormAliyunCLBConfig = ({ ), ]) .nullish(), + domain: z + .string() + .nullish() + .refine((v) => { + if (![RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER].includes(fieldResourceType)) return true; + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), }); const formRule = createSchemaFieldRule(formSchema); @@ -125,6 +133,17 @@ const DeployNodeConfigFormAliyunCLBConfig = ({ + + + } + > + + + ); }; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx index 50f46d44..dd6f6ead 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx @@ -66,7 +66,7 @@ const DeployNodeConfigFormTencentCloudCLBConfig = ({ t("workflow_node.deploy.form.tencentcloud_clb_listener_id.placeholder") ), domain: z - .string({ message: t("workflow_node.deploy.form.tencentcloud_clb_domain.placeholder") }) + .string() .nullish() .refine((v) => RESOURCE_TYPE_RULEDOMAIN !== fieldResourceType || validDomainName(v!, { allowWildcard: true }), 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 6cb3b345..d64fb61d 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -86,6 +86,9 @@ "workflow_node.deploy.form.aliyun_alb_listener_id.label": "Aliyun ALB listener ID", "workflow_node.deploy.form.aliyun_alb_listener_id.placeholder": "Please enter Aliyun ALB listener ID", "workflow_node.deploy.form.aliyun_alb_listener_id.tooltip": "For more information, see https://slb.console.aliyun.com/alb", + "workflow_node.deploy.form.aliyun_alb_snidomain.label": "Aliyun ALB SNI domain (Optional)", + "workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "Please enter Aliyun ALB SNI domain name", + "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "For more information, see https://slb.console.aliyun.com/alb", "workflow_node.deploy.form.aliyun_clb_resource_type.label": "Resource type", "workflow_node.deploy.form.aliyun_clb_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.aliyun_clb_resource_type.option.loadbalancer.label": "CLB load balancer", @@ -99,6 +102,9 @@ "workflow_node.deploy.form.aliyun_clb_listener_port.label": "Aliyun CLB listener port", "workflow_node.deploy.form.aliyun_clb_listener_port.placeholder": "Please enter Aliyun CLB listener port", "workflow_node.deploy.form.aliyun_clb_listener_port.tooltip": "For more information, see https://slb.console.aliyun.com/clb", + "workflow_node.deploy.form.aliyun_clb_snidomain.label": "Aliyun CLB SNI domain (Optional)", + "workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "Please enter Aliyun CLB SNI domain name", + "workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "For more information, see https://slb.console.aliyun.com/clb", "workflow_node.deploy.form.aliyun_cdn_domain.label": "Aliyun CDN domain", "workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "Please enter Aliyun CDN domain name", "workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "For more information, see https://cdn.console.aliyun.com", @@ -262,9 +268,9 @@ "workflow_node.deploy.form.tencentcloud_clb_listener_id.label": "Tencent Cloud CLB listener ID", "workflow_node.deploy.form.tencentcloud_clb_listener_id.placeholder": "Please enter Tencent Cloud CLB listener ID", "workflow_node.deploy.form.tencentcloud_clb_listener_id.tooltip": "For more information, see https://console.tencentcloud.com/clb", - "workflow_node.deploy.form.tencentcloud_clb_snidomain.label": "Tencent Cloud CLB domain (Optional)", - "workflow_node.deploy.form.tencentcloud_clb_snidomain.placeholder": "Please enter Tencent Cloud CLB domain name", - "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "For more information, see https://console.tencentcloud.com/clb", + "workflow_node.deploy.form.tencentcloud_clb_snidomain.label": "Tencent Cloud CLB SNI domain (Optional)", + "workflow_node.deploy.form.tencentcloud_clb_snidomain.placeholder": "Please enter Tencent Cloud CLB SNI domain name", + "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "For more information, see https://console.tencentcloud.com/clb

It is optional. If you want to deploy multiple certificates on the same CLB listener, you can fill in this field.", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.label": "Tencent Cloud CLB domain", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.placeholder": "Please enter Tencent Cloud CLB domain name", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.tooltip": "For more information, see https://console.tencentcloud.com/clb", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 66e3f0e2..7b4a9e01 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -86,6 +86,9 @@ "workflow_node.deploy.form.aliyun_alb_listener_id.label": "阿里云 ALB 监听器 ID", "workflow_node.deploy.form.aliyun_alb_listener_id.placeholder": "请输入阿里云 ALB 监听器 ID", "workflow_node.deploy.form.aliyun_alb_listener_id.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb", + "workflow_node.deploy.form.aliyun_alb_snidomain.label": "阿里云 ALB 扩展域名(可选)", + "workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "请输入阿里云 ALB 扩展域名", + "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb

为空时,将替换监听器的默认证书。", "workflow_node.deploy.form.aliyun_clb_resource_type.label": "证书替换方式", "workflow_node.deploy.form.aliyun_clb_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.aliyun_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听的证书", @@ -96,9 +99,12 @@ "workflow_node.deploy.form.aliyun_clb_loadbalancer_id.label": "阿里云 CLB 负载均衡器 ID", "workflow_node.deploy.form.aliyun_clb_loadbalancer_id.placeholder": "请输入阿里云 CLB 负载均衡器 ID", "workflow_node.deploy.form.aliyun_clb_loadbalancer_id.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb", - "workflow_node.deploy.form.aliyun_clb_listener_id.label": "阿里云 CLB 监听端口", - "workflow_node.deploy.form.aliyun_clb_listener_id.placeholder": "请输入阿里云 CLB 监听端口", - "workflow_node.deploy.form.aliyun_clb_listener_id.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb", + "workflow_node.deploy.form.aliyun_clb_listener_port.label": "阿里云 CLB 监听端口", + "workflow_node.deploy.form.aliyun_clb_listener_port.placeholder": "请输入阿里云 CLB 监听端口", + "workflow_node.deploy.form.aliyun_clb_listener_port.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb", + "workflow_node.deploy.form.aliyun_clb_snidomain.label": "阿里云 CLB 扩展域名(可选)", + "workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "请输入阿里云 CLB 扩展域名", + "workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb

为空时,将替换监听器的默认证书。", "workflow_node.deploy.form.aliyun_cdn_domain.label": "阿里云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "请输入阿里云 CDN 加速域名", "workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "这是什么?请参阅 https://cdn.console.aliyun.com

泛域名表示形式为:*.example.com", @@ -264,7 +270,7 @@ "workflow_node.deploy.form.tencentcloud_clb_listener_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb", "workflow_node.deploy.form.tencentcloud_clb_snidomain.label": "腾讯云 CLB SNI 域名(可选)", "workflow_node.deploy.form.tencentcloud_clb_snidomain.placeholder": "请输入腾讯云 CLB SNI 域名", - "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb", + "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb

为空时,将替换监听器的默认证书。", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.label": "腾讯云 CLB 七层转发规则域名", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.placeholder": "请输入腾讯云 CLB 七层转发规则域名", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb",