From ec91575fefe0b7adafabdf7fc6b58a8f952cd6c7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 14 Nov 2025 18:54:40 +0800 Subject: [PATCH] feat(provider): supoprt cloud product access type in deployment to aliyun waf --- .../certdeploy/deployers/sp_aliyun_waf.go | 4 + .../deployers/sp_tencentcloud_ssldeploy.go | 12 +- .../deployers/sp_tencentcloud_sslupdate.go | 14 +- migrations/1762905600_m0.4.5.go | 37 --- migrations/1763092800_m0.4.5.go | 218 ++++++++++++++++++ .../aliyun-apigw/aliyun_apigw_test.go | 4 +- .../providers/aliyun-waf/aliyun_waf.go | 203 +++++++++++++++- .../providers/aliyun-waf/consts.go | 8 + .../providers/aliyun-waf/internal/client.go | 202 ++++++++++++++++ .../tencentcloud_ssl_deploy.go | 12 +- .../tencentcloud_ssl_update.go | 32 +-- ...loyNodeConfigFieldsProviderAliyunAPIGW.tsx | 20 +- ...eployNodeConfigFieldsProviderAliyunWAF.tsx | 121 ++++++++-- ...figFieldsProviderTencentCloudSSLDeploy.tsx | 14 +- ...figFieldsProviderTencentCloudSSLUpdate.tsx | 22 +- .../i18n/locales/en/nls.workflow.nodes.json | 38 +-- .../i18n/locales/zh/nls.workflow.nodes.json | 40 ++-- 17 files changed, 848 insertions(+), 153 deletions(-) delete mode 100644 migrations/1762905600_m0.4.5.go create mode 100644 migrations/1763092800_m0.4.5.go create mode 100644 pkg/core/ssl-deployer/providers/aliyun-waf/consts.go diff --git a/internal/certdeploy/deployers/sp_aliyun_waf.go b/internal/certdeploy/deployers/sp_aliyun_waf.go index 03a2074e..247ecd07 100644 --- a/internal/certdeploy/deployers/sp_aliyun_waf.go +++ b/internal/certdeploy/deployers/sp_aliyun_waf.go @@ -22,7 +22,11 @@ func init() { ResourceGroupId: credentials.ResourceGroupId, Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), ServiceVersion: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), + ServiceType: xmaps.GetString(options.ProviderExtendedConfig, "serviceType"), InstanceId: xmaps.GetString(options.ProviderExtendedConfig, "instanceId"), + ResourceProduct: xmaps.GetString(options.ProviderExtendedConfig, "resourceProduct"), + ResourceId: xmaps.GetString(options.ProviderExtendedConfig, "resourceId"), + ResourcePort: xmaps.GetOrDefaultInt32(options.ProviderExtendedConfig, "resourcePort", 443), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/internal/certdeploy/deployers/sp_tencentcloud_ssldeploy.go b/internal/certdeploy/deployers/sp_tencentcloud_ssldeploy.go index dcb13e42..e8643c1c 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_ssldeploy.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_ssldeploy.go @@ -20,12 +20,12 @@ func init() { } provider, err := tencentcloudssldeploy.NewSSLDeployerProvider(&tencentcloudssldeploy.SSLDeployerProviderConfig{ - SecretId: credentials.SecretId, - SecretKey: credentials.SecretKey, - Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), - Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), - ResourceType: xmaps.GetString(options.ProviderExtendedConfig, "resourceType"), - ResourceIds: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string, _ int) bool { return s != "" }), + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + ResourceProduct: xmaps.GetString(options.ProviderExtendedConfig, "resourceProduct"), + ResourceIds: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string, _ int) bool { return s != "" }), }) return provider, err }) diff --git a/internal/certdeploy/deployers/sp_tencentcloud_sslupdate.go b/internal/certdeploy/deployers/sp_tencentcloud_sslupdate.go index 26430b14..f472d6d1 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_sslupdate.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_sslupdate.go @@ -20,13 +20,13 @@ func init() { } provider, err := tencentcloudsslupdate.NewSSLDeployerProvider(&tencentcloudsslupdate.SSLDeployerProviderConfig{ - SecretId: credentials.SecretId, - SecretKey: credentials.SecretKey, - Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), - CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), - IsReplaced: xmaps.GetBool(options.ProviderExtendedConfig, "isReplaced"), - ResourceTypes: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceTypes"), ";"), func(s string, _ int) bool { return s != "" }), - ResourceRegions: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceRegions"), ";"), func(s string, _ int) bool { return s != "" }), + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), + IsReplaced: xmaps.GetBool(options.ProviderExtendedConfig, "isReplaced"), + ResourceProducts: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceProducts"), ";"), func(s string, _ int) bool { return s != "" }), + ResourceRegions: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "resourceRegions"), ";"), func(s string, _ int) bool { return s != "" }), }) return provider, err }) diff --git a/migrations/1762905600_m0.4.5.go b/migrations/1762905600_m0.4.5.go deleted file mode 100644 index a6de954c..00000000 --- a/migrations/1762905600_m0.4.5.go +++ /dev/null @@ -1,37 +0,0 @@ -package migrations - -import ( - "github.com/pocketbase/pocketbase/core" - m "github.com/pocketbase/pocketbase/migrations" -) - -func init() { - m.Register(func(app core.App) error { - tracer := NewTracer("v0.4.5") - tracer.Printf("go ...") - - // adapt to new workflow data structure - { - if _, err := app.DB().NewQuery("UPDATE workflow SET graphDraft = REPLACE(graphDraft, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil { - return err - } - - if _, err := app.DB().NewQuery("UPDATE workflow SET graphContent = REPLACE(graphContent, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil { - return err - } - - if _, err := app.DB().NewQuery("UPDATE workflow_run SET graph = REPLACE(graph, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil { - return err - } - - if _, err := app.DB().NewQuery("UPDATE workflow_output SET nodeConfig = REPLACE(nodeConfig, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil { - return err - } - } - - tracer.Printf("done") - return nil - }, func(app core.App) error { - return nil - }) -} diff --git a/migrations/1763092800_m0.4.5.go b/migrations/1763092800_m0.4.5.go new file mode 100644 index 00000000..dd19ffd1 --- /dev/null +++ b/migrations/1763092800_m0.4.5.go @@ -0,0 +1,218 @@ +package migrations + +import ( + "github.com/go-viper/mapstructure/v2" + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + tracer := NewTracer("v0.4.5") + tracer.Printf("go ...") + + // adapt to new workflow data structure + { + type dWorkflowNode struct { + Id string `json:"id"` + Type string `json:"type"` + Data map[string]any `json:"data"` + Blocks []*dWorkflowNode `json:"blocks,omitempty,omitzero"` + } + + var deepMigrateNode func(node *dWorkflowNode) (_node *dWorkflowNode, _migrated bool) + var deepMigrateNodes func(nodes []*dWorkflowNode) (_nodes []*dWorkflowNode, _migrated bool) + deepMigrateNode = func(node *dWorkflowNode) (*dWorkflowNode, bool) { + migrated := false + + if node.Type == "bizDeploy" { + if node.Data != nil { + if _, ok := node.Data["config"]; ok { + nodeCfg := node.Data["config"].(map[string]any) + if nodeCfg["provider"] == "tencentcloud-ssldeploy" { + if nodeCfg["providerConfig"] != nil { + providerCfg := nodeCfg["providerConfig"].(map[string]any) + providerCfg["resourceProduct"] = providerCfg["resourceType"] + delete(providerCfg, "resourceType") + nodeCfg["providerConfig"] = providerCfg + + node.Data["config"] = nodeCfg + migrated = true + } + } else if nodeCfg["provider"] == "tencentcloud-sslupdate" { + if nodeCfg["providerConfig"] != nil { + providerCfg := nodeCfg["providerConfig"].(map[string]any) + providerCfg["resourceProducts"] = providerCfg["resourceTypes"] + delete(providerCfg, "resourceTypes") + nodeCfg["providerConfig"] = providerCfg + + node.Data["config"] = nodeCfg + migrated = true + } + } else if nodeCfg["provider"] == "aliyun-waf" { + if nodeCfg["providerConfig"] != nil { + providerCfg := nodeCfg["providerConfig"].(map[string]any) + providerCfg["serviceType"] = "cname" + nodeCfg["providerConfig"] = providerCfg + + node.Data["config"] = nodeCfg + migrated = true + } + } + } + } + } + + if len(node.Blocks) > 0 { + if newBlocks, changed := deepMigrateNodes(node.Blocks); changed { + node.Blocks = newBlocks + migrated = true + } + } + + return node, migrated + } + deepMigrateNodes = func(nodes []*dWorkflowNode) ([]*dWorkflowNode, bool) { + migrated := false + + for i, node := range nodes { + if newNode, changed := deepMigrateNode(node); changed { + nodes[i] = newNode + migrated = true + } + } + + return nodes, migrated + } + + // update collection `workflow` + // - migrate field `graphDraft` / `graphContent` + { + collection, err := app.FindCollectionByNameOrId("tovyif5ax6j62ur") + if err != nil { + return err + } + + records, err := app.FindAllRecords(collection) + if err != nil { + return err + } + + for _, record := range records { + changed := false + + graphDraft := make(map[string]any) + if err := record.UnmarshalJSONField("graphDraft", &graphDraft); err == nil { + if _, ok := graphDraft["nodes"]; ok { + nodes := make([]*dWorkflowNode, 0) + if err := mapstructure.Decode(graphDraft["nodes"], &nodes); err != nil { + return err + } + + if newNodes, migrated := deepMigrateNodes(nodes); migrated { + graphDraft["nodes"] = newNodes + record.Set("graphDraft", graphDraft) + changed = true + } + } + } + + graphContent := make(map[string]any) + if err := record.UnmarshalJSONField("graphContent", &graphContent); err == nil { + if _, ok := graphContent["nodes"]; ok { + nodes := make([]*dWorkflowNode, 0) + if err := mapstructure.Decode(graphContent["nodes"], &nodes); err != nil { + return err + } + + if newNodes, migrated := deepMigrateNodes(nodes); migrated { + graphContent["nodes"] = newNodes + record.Set("graphContent", graphContent) + changed = true + } + } + } + + if changed { + if err := app.Save(record); err != nil { + return err + } + + tracer.Printf("record #%s in collection '%s' updated", record.Id, collection.Name) + } + } + + if _, err := app.DB().NewQuery("UPDATE workflow SET graphDraft = REPLACE(graphDraft, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil { + return err + } + + if _, err := app.DB().NewQuery("UPDATE workflow SET graphContent = REPLACE(graphContent, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil { + return err + } + } + + // update collection `workflow_run` + // - migrate field `graph` + { + collection, err := app.FindCollectionByNameOrId("qjp8lygssgwyqyz") + if err != nil { + return err + } + + records, err := app.FindAllRecords(collection) + if err != nil { + return err + } + + for _, record := range records { + changed := false + + graph := make(map[string]any) + if err := record.UnmarshalJSONField("graph", &graph); err == nil { + if _, ok := graph["nodes"]; ok { + nodes := make([]*dWorkflowNode, 0) + if err := mapstructure.Decode(graph["nodes"], &nodes); err != nil { + return err + } + + if newNodes, migrated := deepMigrateNodes(nodes); migrated { + graph["nodes"] = newNodes + record.Set("graph", graph) + changed = true + } + } + } + + if changed { + if err := app.Save(record); err != nil { + return err + } + + tracer.Printf("record #%s in collection '%s' updated", record.Id, collection.Name) + } + } + + if _, err := app.DB().NewQuery("UPDATE workflow_run SET graph = REPLACE(graph, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil { + return err + } + } + + // update collection `workflow_output` + // - migrate field `nodeConfig` + { + if _, err := app.DB().NewQuery("UPDATE workflow_output SET nodeConfig = REPLACE(nodeConfig, '\"matchPattern\"', '\"domainMatchPattern\"')").Execute(); err != nil { + return err + } + + if _, err := app.DB().NewQuery("UPDATE workflow_output SET nodeConfig = REPLACE(nodeConfig, '\"resourceType\"', '\"resourceProduct\"') WHERE nodeConfig LIKE '%\"provider\":\"tencentcloud-ssldeploy\"%' OR nodeConfig LIKE '%\"provider\":\"tencentcloud-sslupdate\"%'").Execute(); err != nil { + return err + } + } + } + + tracer.Printf("done") + return nil + }, func(app core.App) error { + return nil + }) +} 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 2ed1b052..00bd1b98 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 @@ -46,9 +46,9 @@ Shell command to run this test: --CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_ACCESSKEYID="your-access-key-id" \ --CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_ACCESSKEYSECRET="your-access-key-secret" \ --CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_REGION="cn-hangzhou" \ + --CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_SERVICETYPE="cloudnative" \ --CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_GATEWAYID="your-api-gateway-id" \ --CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_GROUPID="your-api-group-id" \ - --CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_SERVICETYPE="cloudnative" \ --CERTIMATE_SSLDEPLOYER_ALIYUNAPIGW_DOMAIN="example.com" */ func TestDeploy(t *testing.T) { @@ -62,9 +62,9 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("SERVICETYPE: %v", fServiceType), fmt.Sprintf("GATEWAYID: %v", fGatewayId), fmt.Sprintf("GROUPID: %v", fGroupId), - fmt.Sprintf("SERVICETYPE: %v", fServiceType), fmt.Sprintf("DOMAIN: %v", fDomain), }, "\n")) 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 f12321f9..dfe66d57 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go +++ b/pkg/core/ssl-deployer/providers/aliyun-waf/aliyun_waf.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "strings" + "time" aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" "github.com/alibabacloud-go/tea/dara" @@ -28,10 +29,22 @@ type SSLDeployerProviderConfig struct { // 阿里云地域。 Region string `json:"region"` // 服务版本。 + // 可取值 "3.0"。 ServiceVersion string `json:"serviceVersion"` + // 服务类型。 + ServiceType string `json:"serviceType"` // WAF 实例 ID。 InstanceId string `json:"instanceId"` - // 接入域名(支持泛域名)。 + // 云产品类型。 + // 服务类型为 [SERVICE_TYPE_CLOUDRESOURCE] 时必填。 + ResourceProduct string `json:"resourceProduct,omitempty"` + // 云产品资源 ID。 + // 服务类型为 [SERVICE_TYPE_CLOUDRESOURCE] 时必填。 + ResourceId string `json:"resourceId,omitempty"` + // 云产品资源端口。 + // 服务类型为 [SERVICE_TYPE_CLOUDRESOURCE] 时必填。 + ResourcePort int32 `json:"resourcePort,omitempty"` + // 扩展域名(支持泛域名)。 Domain string `json:"domain,omitempty"` } @@ -85,10 +98,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.InstanceId == "" { - return nil, errors.New("config `instanceId` is required") - } - switch d.config.ServiceVersion { case "3", "3.0": if err := d.deployToWAF3(ctx, certPEM, privkeyPEM); err != nil { @@ -103,6 +112,10 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.InstanceId == "" { + return errors.New("config `instanceId` is required") + } + // 上传证书 upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM) if err != nil { @@ -111,8 +124,180 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 根据接入方式决定部署方式 + switch d.config.ServiceType { + case SERVICE_TYPE_CLOUDRESOURCE: + certId := upres.ExtendedData["CertIdentifier"].(string) + if err := d.deployToWAF3WithCloudResource(ctx, certId); err != nil { + return err + } + + case SERVICE_TYPE_CNAME: + certId := upres.ExtendedData["CertIdentifier"].(string) + if err := d.deployToWAF3WithCNAME(ctx, certId); err != nil { + return err + } + + default: + return fmt.Errorf("unsupported service version '%s'", d.config.ServiceVersion) + } + + return nil +} + +func (d *SSLDeployerProvider) deployToWAF3WithCloudResource(ctx context.Context, cloudCertId string) error { + if d.config.ResourceProduct == "" { + return errors.New("config `resourceProduct` is required") + } + if d.config.ResourceId == "" { + return errors.New("config `resourceId` is required") + } + if d.config.ResourcePort == 0 { + d.config.ResourcePort = 443 + } + + // 查询已同步的云产品资产 + // REF: https://www.alibabacloud.com/help/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describeproductinstances + var resourceInstance *aliwaf.DescribeProductInstancesResponseBodyProductInstances + var resourceInstancePort *aliwaf.DescribeProductInstancesResponseBodyProductInstancesResourcePorts + describeProductInstancesReq := &aliwaf.DescribeProductInstancesRequest{ + ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + RegionId: tea.String(d.config.Region), + InstanceId: tea.String(d.config.InstanceId), + ResourceProduct: tea.String(d.config.ResourceProduct), + ResourceInstanceId: tea.String(d.config.ResourceId), + } + describeProductInstancesResp, err := d.sdkClient.DescribeProductInstancesWithContext(ctx, describeProductInstancesReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'waf.DescribeProductInstances'", slog.Any("request", describeProductInstancesReq), slog.Any("response", describeProductInstancesResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'waf.DescribeProductInstances': %w", err) + } else if len(describeProductInstancesResp.Body.ProductInstances) == 0 { + return fmt.Errorf("cloud not find waf '%s' cloud resource '%s %s'", d.config.InstanceId, d.config.ResourceProduct, d.config.ResourceId) + } else { + resourceInstance = describeProductInstancesResp.Body.ProductInstances[0] + + resourceInstancePort, _ = lo.Find(resourceInstance.ResourcePorts, func(p *aliwaf.DescribeProductInstancesResponseBodyProductInstancesResourcePorts) bool { + return tea.Int32Value(p.Port) == d.config.ResourcePort + }) + if resourceInstancePort == nil { + return fmt.Errorf("could not find waf '%s' cloud resource '%s %s:%d'", d.config.InstanceId, d.config.ResourceProduct, d.config.ResourceId, d.config.ResourcePort) + } + } + + // 查询云产品实例的证书列表 + var resourceInstanceCertificates []*aliwaf.DescribeResourceInstanceCertsResponseBodyCerts = make([]*aliwaf.DescribeResourceInstanceCertsResponseBodyCerts, 0) + describeResourceInstanceCertsPageNumber := 1 + describeResourceInstanceCertsPageSize := 10 + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + describeResourceInstanceCertsReq := &aliwaf.DescribeResourceInstanceCertsRequest{ + ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + InstanceId: tea.String(d.config.InstanceId), + ResourceInstanceId: tea.String(d.config.ResourceId), + PageNumber: tea.Int64(int64(describeResourceInstanceCertsPageNumber)), + PageSize: tea.Int64(int64(describeResourceInstanceCertsPageSize)), + } + describeResourceInstanceCertsResp, err := d.sdkClient.DescribeResourceInstanceCertsWithContext(ctx, describeResourceInstanceCertsReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'waf.DescribeResourceInstanceCerts'", slog.Any("request", describeResourceInstanceCertsReq), slog.Any("response", describeResourceInstanceCertsResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'wafw.DescribeResourceInstanceCerts': %w", err) + } + + if describeResourceInstanceCertsResp.Body == nil { + break + } + + resourceInstanceCertificates = append(resourceInstanceCertificates, describeResourceInstanceCertsResp.Body.Certs...) + + if len(describeResourceInstanceCertsResp.Body.Certs) < describeResourceInstanceCertsPageSize { + break + } + + describeResourceInstanceCertsPageNumber++ + } + + // 生成请求参数 + modifyCloudResourceReq := &aliwaf.ModifyCloudResourceRequest{ + ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), + RegionId: tea.String(d.config.Region), + Listen: &aliwaf.ModifyCloudResourceRequestListen{ + ResourceProduct: resourceInstance.ResourceProduct, + ResourceInstanceId: resourceInstance.ResourceInstanceId, + Protocol: tea.String("https"), + Port: resourceInstancePort.Port, + Certificates: lo.Map(resourceInstancePort.Certificates, func(c *aliwaf.DescribeProductInstancesResponseBodyProductInstancesResourcePortsCertificates, _ int) *aliwaf.ModifyCloudResourceRequestListenCertificates { + return &aliwaf.ModifyCloudResourceRequestListenCertificates{ + CertificateId: c.CertificateId, + AppliedType: c.AppliedType, + } + }), + }, + } if d.config.Domain == "" { - // 未指定接入域名,只需替换默认证书即可 + // 未指定扩展域名,只需替换默认证书 + const certAppliedTypeDefault = "default" + for _, certItem := range modifyCloudResourceReq.Listen.Certificates { + if tea.StringValue(certItem.AppliedType) == certAppliedTypeDefault && + tea.StringValue(certItem.CertificateId) == cloudCertId { + return nil + } + } + + modifyCloudResourceReq.Listen.Certificates = lo.Filter(modifyCloudResourceReq.Listen.Certificates, func(c *aliwaf.ModifyCloudResourceRequestListenCertificates, _ int) bool { + return tea.StringValue(c.AppliedType) != certAppliedTypeDefault + }) + modifyCloudResourceReq.Listen.Certificates = append(modifyCloudResourceReq.Listen.Certificates, &aliwaf.ModifyCloudResourceRequestListenCertificates{ + CertificateId: tea.String(cloudCertId), + AppliedType: tea.String(certAppliedTypeDefault), + }) + } else { + // 指定扩展域名,需替换扩展证书 + const certAppliedTypeExtension = "extension" + + modifyCloudResourceReq.Listen.Certificates = append(modifyCloudResourceReq.Listen.Certificates, &aliwaf.ModifyCloudResourceRequestListenCertificates{ + CertificateId: tea.String(cloudCertId), + AppliedType: tea.String(certAppliedTypeExtension), + }) + } + + // 过滤掉不存在或已过期的证书,防止接口报错 + modifyCloudResourceReq.Listen.Certificates = lo.Filter(modifyCloudResourceReq.Listen.Certificates, func(c *aliwaf.ModifyCloudResourceRequestListenCertificates, _ int) bool { + if tea.StringValue(c.CertificateId) == cloudCertId { + return true + } + + resourceInstanceCert, _ := lo.Find(resourceInstanceCertificates, func(r *aliwaf.DescribeResourceInstanceCertsResponseBodyCerts) bool { + cId := tea.StringValue(c.CertificateId) + rId := tea.StringValue(r.CertIdentifier) + return cId == rId || strings.Split(cId, "-")[0] == strings.Split(rId, "-")[0] + }) + if resourceInstanceCert != nil { + certNotAfter := time.Unix(tea.Int64Value(resourceInstanceCert.AfterDate)/1000, 0) + return certNotAfter.After(time.Now()) + } + + return false + }) + + // 修改云产品接入的配置 + // REF: https://www.alibabacloud.com/help/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-modifycloudresource + modifyCloudResourceResp, err := d.sdkClient.ModifyCloudResourceWithContext(ctx, modifyCloudResourceReq, &dara.RuntimeOptions{}) + d.logger.Debug("sdk request 'waf.ModifyCloudResource'", slog.Any("request", modifyCloudResourceReq), slog.Any("response", modifyCloudResourceResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'waf.ModifyCloudResource': %w", err) + } + + return nil +} + +func (d *SSLDeployerProvider) deployToWAF3WithCNAME(ctx context.Context, cloudCertId string) error { + if d.config.Domain == "" { + // 未指定扩展域名,只需替换默认证书 // 查询默认 SSL/TLS 设置 // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedefaulthttps @@ -133,7 +318,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, ResourceManagerResourceGroupId: lo.EmptyableToPtr(d.config.ResourceGroupId), RegionId: tea.String(d.config.Region), InstanceId: tea.String(d.config.InstanceId), - CertId: tea.String(upres.ExtendedData["CertIdentifier"].(string)), + CertId: tea.String(cloudCertId), TLSVersion: tea.String("tlsv1"), EnableTLSv3: tea.Bool(true), } @@ -151,7 +336,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, return fmt.Errorf("failed to execute sdk request 'waf.ModifyDefaultHttps': %w", err) } } else { - // 指定接入域名 + // 指定扩展域名,需替换扩展证书 // 查询 CNAME 接入详情 // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedomaindetail @@ -172,7 +357,7 @@ func (d *SSLDeployerProvider) deployToWAF3(ctx context.Context, certPEM string, 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))}, + Listen: &aliwaf.ModifyDomainRequestListen{CertId: tea.String(cloudCertId)}, Redirect: &aliwaf.ModifyDomainRequestRedirect{Loadbalance: tea.String("iphash")}, } modifyDomainReq = _assign(modifyDomainReq, describeDomainDetailResp.Body) diff --git a/pkg/core/ssl-deployer/providers/aliyun-waf/consts.go b/pkg/core/ssl-deployer/providers/aliyun-waf/consts.go new file mode 100644 index 00000000..f565fb36 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/aliyun-waf/consts.go @@ -0,0 +1,8 @@ +package aliyunwaf + +const ( + // 服务类型:云产品接入。 + SERVICE_TYPE_CLOUDRESOURCE = "cloudresource" + // 服务类型:CNAME 接入。 + SERVICE_TYPE_CNAME = "cname" +) diff --git a/pkg/core/ssl-deployer/providers/aliyun-waf/internal/client.go b/pkg/core/ssl-deployer/providers/aliyun-waf/internal/client.go index 7c056cd6..abdc117d 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-waf/internal/client.go +++ b/pkg/core/ssl-deployer/providers/aliyun-waf/internal/client.go @@ -123,6 +123,208 @@ func (client *WafClient) DescribeDomainDetailWithContext(ctx context.Context, re return _result, _err } +func (client *WafClient) DescribeProductInstancesWithContext(ctx context.Context, request *aliwaf.DescribeProductInstancesRequest, runtime *dara.RuntimeOptions) (_result *aliwaf.DescribeProductInstancesResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.InstanceId) { + query["InstanceId"] = request.InstanceId + } + + if !dara.IsNil(request.OwnerUserId) { + query["OwnerUserId"] = request.OwnerUserId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.RegionId) { + query["RegionId"] = request.RegionId + } + + if !dara.IsNil(request.ResourceInstanceAccessStatus) { + query["ResourceInstanceAccessStatus"] = request.ResourceInstanceAccessStatus + } + + if !dara.IsNil(request.ResourceInstanceId) { + query["ResourceInstanceId"] = request.ResourceInstanceId + } + + if !dara.IsNil(request.ResourceInstanceIp) { + query["ResourceInstanceIp"] = request.ResourceInstanceIp + } + + if !dara.IsNil(request.ResourceInstanceName) { + query["ResourceInstanceName"] = request.ResourceInstanceName + } + + if !dara.IsNil(request.ResourceIp) { + query["ResourceIp"] = request.ResourceIp + } + + if !dara.IsNil(request.ResourceManagerResourceGroupId) { + query["ResourceManagerResourceGroupId"] = request.ResourceManagerResourceGroupId + } + + if !dara.IsNil(request.ResourceName) { + query["ResourceName"] = request.ResourceName + } + + if !dara.IsNil(request.ResourceProduct) { + query["ResourceProduct"] = request.ResourceProduct + } + + if !dara.IsNil(request.ResourceRegionId) { + query["ResourceRegionId"] = request.ResourceRegionId + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeProductInstances"), + Version: dara.String("2021-10-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 = &aliwaf.DescribeProductInstancesResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + +func (client *WafClient) DescribeResourceInstanceCertsWithContext(ctx context.Context, request *aliwaf.DescribeResourceInstanceCertsRequest, runtime *dara.RuntimeOptions) (_result *aliwaf.DescribeResourceInstanceCertsResponse, _err error) { + _err = request.Validate() + if _err != nil { + return _result, _err + } + query := map[string]interface{}{} + + if !dara.IsNil(request.InstanceId) { + query["InstanceId"] = request.InstanceId + } + + if !dara.IsNil(request.PageNumber) { + query["PageNumber"] = request.PageNumber + } + + if !dara.IsNil(request.PageSize) { + query["PageSize"] = request.PageSize + } + + if !dara.IsNil(request.RegionId) { + query["RegionId"] = request.RegionId + } + + if !dara.IsNil(request.ResourceInstanceId) { + query["ResourceInstanceId"] = request.ResourceInstanceId + } + + if !dara.IsNil(request.ResourceManagerResourceGroupId) { + query["ResourceManagerResourceGroupId"] = request.ResourceManagerResourceGroupId + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("DescribeResourceInstanceCerts"), + Version: dara.String("2021-10-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 = &aliwaf.DescribeResourceInstanceCertsResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + +func (client *WafClient) ModifyCloudResourceWithContext(ctx context.Context, tmpReq *aliwaf.ModifyCloudResourceRequest, runtime *dara.RuntimeOptions) (_result *aliwaf.ModifyCloudResourceResponse, _err error) { + _err = tmpReq.Validate() + if _err != nil { + return _result, _err + } + + request := &aliwaf.ModifyCloudResourceShrinkRequest{} + openapiutil.Convert(tmpReq, request) + + if !dara.IsNil(tmpReq.Listen) { + request.ListenShrink = openapiutil.ArrayToStringWithSpecifiedStyle(tmpReq.Listen, dara.String("Listen"), dara.String("json")) + } + + if !dara.IsNil(tmpReq.Redirect) { + request.RedirectShrink = openapiutil.ArrayToStringWithSpecifiedStyle(tmpReq.Redirect, dara.String("Redirect"), dara.String("json")) + } + + query := map[string]interface{}{} + + if !dara.IsNil(request.InstanceId) { + query["InstanceId"] = request.InstanceId + } + + if !dara.IsNil(request.ListenShrink) { + query["Listen"] = request.ListenShrink + } + + if !dara.IsNil(request.RedirectShrink) { + query["Redirect"] = request.RedirectShrink + } + + if !dara.IsNil(request.RegionId) { + query["RegionId"] = request.RegionId + } + + if !dara.IsNil(request.ResourceManagerResourceGroupId) { + query["ResourceManagerResourceGroupId"] = request.ResourceManagerResourceGroupId + } + + req := &openapiutil.OpenApiRequest{ + Query: openapiutil.Query(query), + } + params := &openapiutil.Params{ + Action: dara.String("ModifyCloudResource"), + Version: dara.String("2021-10-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 = &aliwaf.ModifyCloudResourceResponse{} + _body, _err := client.CallApiWithCtx(ctx, params, req, runtime) + if _err != nil { + return _result, _err + } + _err = dara.Convert(_body, &_result) + return _result, _err +} + func (client *WafClient) ModifyDefaultHttpsWithContext(ctx context.Context, request *aliwaf.ModifyDefaultHttpsRequest, runtime *dara.RuntimeOptions) (_result *aliwaf.ModifyDefaultHttpsResponse, _err error) { _err = request.Validate() if _err != nil { 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 b7fd0abd..d1d52365 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 @@ -26,9 +26,9 @@ type SSLDeployerProviderConfig struct { Endpoint string `json:"endpoint,omitempty"` // 腾讯云地域。 Region string `json:"region"` - // 云资源类型。 - ResourceType string `json:"resourceType"` - // 云资源 ID 数组。 + // 云产品类型。 + ResourceProduct string `json:"resourceProduct"` + // 云产品资源 ID 数组。 ResourceIds []string `json:"resourceIds,omitempty"` } @@ -79,8 +79,8 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.ResourceType == "" { - return nil, errors.New("config `resourceType` is required") + if d.config.ResourceProduct == "" { + return nil, errors.New("config `resourceProduct` is required") } if len(d.config.ResourceIds) == 0 { return nil, errors.New("config `resourceIds` is required") @@ -98,7 +98,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // REF: https://cloud.tencent.com/document/api/400/91667 deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest() deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) - deployCertificateInstanceReq.ResourceType = common.StringPtr(d.config.ResourceType) + deployCertificateInstanceReq.ResourceType = common.StringPtr(d.config.ResourceProduct) deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(d.config.ResourceIds) deployCertificateInstanceReq.Status = common.Int64Ptr(1) deployCertificateInstanceResp, err := d.sdkClient.DeployCertificateInstance(deployCertificateInstanceReq) 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 925ca2a4..bcc63b26 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 @@ -29,9 +29,9 @@ type SSLDeployerProviderConfig struct { CertificateId string `json:"certificateId"` // 是否替换原有证书(即保持原证书 ID 不变)。 IsReplaced bool `json:"isReplaced,omitempty"` - // 云资源类型数组。 - ResourceTypes []string `json:"resourceTypes"` - // 云资源地域数组。 + // 云产品类型数组。 + ResourceProducts []string `json:"resourceProducts"` + // 云产品地域数组。 ResourceRegions []string `json:"resourceRegions"` } @@ -85,8 +85,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke if d.config.CertificateId == "" { return nil, errors.New("config `certificateId` is required") } - if len(d.config.ResourceTypes) == 0 { - return nil, errors.New("config `resourceTypes` is required") + if len(d.config.ResourceProducts) == 0 { + return nil, errors.New("config `resourceProducts` is required") } if d.config.IsReplaced { @@ -124,8 +124,8 @@ func (d *SSLDeployerProvider) executeUpdateCertificateInstance(ctx context.Conte updateCertificateInstanceReq := tcssl.NewUpdateCertificateInstanceRequest() updateCertificateInstanceReq.OldCertificateId = common.StringPtr(d.config.CertificateId) updateCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) - updateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceTypes) - updateCertificateInstanceReq.ResourceTypesRegions = wrapResourceTypeRegions(d.config.ResourceTypes, d.config.ResourceRegions) + updateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceProducts) + updateCertificateInstanceReq.ResourceTypesRegions = wrapResourceProductRegions(d.config.ResourceProducts, d.config.ResourceRegions) updateCertificateInstanceResp, err := d.sdkClient.UpdateCertificateInstance(updateCertificateInstanceReq) d.logger.Debug("sdk request 'ssl.UpdateCertificateInstance'", slog.Any("request", updateCertificateInstanceReq), slog.Any("response", updateCertificateInstanceResp)) if err != nil { @@ -200,8 +200,8 @@ func (d *SSLDeployerProvider) executeUploadUpdateCertificateInstance(ctx context uploadUpdateCertificateInstanceReq.OldCertificateId = common.StringPtr(d.config.CertificateId) uploadUpdateCertificateInstanceReq.CertificatePublicKey = common.StringPtr(certPEM) uploadUpdateCertificateInstanceReq.CertificatePrivateKey = common.StringPtr(privkeyPEM) - uploadUpdateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceTypes) - uploadUpdateCertificateInstanceReq.ResourceTypesRegions = wrapResourceTypeRegions(d.config.ResourceTypes, d.config.ResourceRegions) + uploadUpdateCertificateInstanceReq.ResourceTypes = common.StringPtrs(d.config.ResourceProducts) + uploadUpdateCertificateInstanceReq.ResourceTypesRegions = wrapResourceProductRegions(d.config.ResourceProducts, d.config.ResourceRegions) uploadUpdateCertificateInstanceResp, err := d.sdkClient.UploadUpdateCertificateInstance(uploadUpdateCertificateInstanceReq) d.logger.Debug("sdk request 'ssl.UploadUpdateCertificateInstance'", slog.Any("request", uploadUpdateCertificateInstanceReq), slog.Any("response", uploadUpdateCertificateInstanceResp)) if err != nil { @@ -278,19 +278,19 @@ func createSDKClient(secretId, secretKey, endpoint string) (*internal.SslClient, return client, nil } -func wrapResourceTypeRegions(resourceTypes, resourceRegions []string) []*tcssl.ResourceTypeRegions { - if len(resourceTypes) == 0 || len(resourceRegions) == 0 { +func wrapResourceProductRegions(resourceProducts, resourceRegions []string) []*tcssl.ResourceTypeRegions { + if len(resourceProducts) == 0 || len(resourceRegions) == 0 { return nil } - // 仅以下云资源类型支持地域 - resourceTypesRequireRegion := []string{"apigateway", "clb", "cos", "tcb", "tke", "tse", "waf"} + // 仅以下云产品类型支持地域 + resourceProductsRequireRegion := []string{"apigateway", "clb", "cos", "tcb", "tke", "tse", "waf"} temp := make([]*tcssl.ResourceTypeRegions, 0) - for _, resourceType := range resourceTypes { - if slices.Contains(resourceTypesRequireRegion, resourceType) { + for _, resourceProduct := range resourceProducts { + if slices.Contains(resourceProductsRequireRegion, resourceProduct) { temp = append(temp, &tcssl.ResourceTypeRegions{ - ResourceType: common.StringPtr(resourceType), + ResourceType: common.StringPtr(resourceProduct), Regions: common.StringPtrs(resourceRegions), }) } diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx index 78e63827..3bd1a473 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunAPIGW.tsx @@ -31,6 +31,16 @@ const BizDeployNodeConfigFieldsProviderAliyunAPIGW = () => { return ( <> + } + > + + + { - } - > - - - { const { i18n, t } = useTranslation(); @@ -15,8 +19,11 @@ const BizDeployNodeConfigFieldsProviderAliyunWAF = () => { [parentNamePath]: getSchema({ i18n }), }); const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); const initialValues = getInitialValues(); + const fieldServiceType = Form.useWatch([parentNamePath, "serviceType"], formInst); + return ( <> { + + + + { + + + ({ value }))} + placeholder={t("workflow_node.deploy.form.aliyun_waf_resource_product.placeholder")} + filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())} + /> + + + + + + + + + + + >> => { region: "", serviceVersion: "3.0", instanceId: "", + resourceProduct: "", + resourceId: "", + resourcePort: 443, }; }; const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { const { t } = i18n; - return z.object({ - region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_region.placeholder")), - serviceVersion: z.literal("3.0", t("workflow_node.deploy.form.aliyun_waf_service_version.placeholder")), - instanceId: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder")), - domain: z - .string() - .nullish() - .refine((v) => { - return !v || validDomainName(v!, { allowWildcard: true }); - }, t("common.errmsg.domain_invalid")), - }); + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_region.placeholder")), + serviceVersion: z.literal("3.0", t("workflow_node.deploy.form.aliyun_waf_service_version.placeholder")), + serviceType: z.literal([SERVICE_TYPE_CLOUDRESOURCE, SERVICE_TYPE_CNAME], t("workflow_node.deploy.form.aliyun_waf_service_type.placeholder")), + instanceId: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder")), + resourceProduct: z.string().nullish(), + resourceId: z.string().nullish(), + resourcePort: z.preprocess((v) => (v == null || v === "" ? void 0 : Number(v)), z.number().nullish()), + domain: z + .string() + .nullish() + .refine((v) => { + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), + }) + .superRefine((values, ctx) => { + switch (values.serviceType) { + case SERVICE_TYPE_CLOUDRESOURCE: + { + if (!values.resourceProduct) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_waf_resource_product.placeholder"), + path: ["resourceProduct"], + }); + } + + if (!values.resourceId) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_waf_resource_id.placeholder"), + path: ["resourceId"], + }); + } + + if (!validPortNumber(values.resourcePort!)) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_waf_resource_port.placeholder"), + path: ["resourcePort"], + }); + } + } + break; + } + }); }; const _default = Object.assign(BizDeployNodeConfigFieldsProviderAliyunWAF, { diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy.tsx index e5c9a2d3..3e16b89b 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy.tsx @@ -47,15 +47,15 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy = () => { } + tooltip={} > ({ value }))} - placeholder={t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.placeholder")} + placeholder={t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.placeholder")} filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())} /> @@ -83,7 +83,7 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy = () => { const getInitialValues = (): Nullish>> => { return { region: "", - resourceType: "", + resourceProduct: "", resourceIds: "", }; }; @@ -94,7 +94,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z.object({ endpoint: z.string().nullish(), region: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_ssldeploy_region.placeholder")), - resourceType: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.placeholder")), + resourceProduct: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.placeholder")), resourceIds: z.string().refine((v) => { if (!v) return false; return String(v) diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate.tsx index b43e2b2a..44eace7b 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate.tsx @@ -47,17 +47,17 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate = () => { } + tooltip={} > @@ -96,7 +96,7 @@ const BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate = () => { const getInitialValues = (): Nullish>> => { return { certificateId: "", - resourceTypes: "", + resourceProducts: "", }; }; @@ -106,12 +106,12 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z.object({ endpoint: z.string().nullish(), certificateId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.placeholder")), - resourceTypes: z.string().refine((v) => { + resourceProducts: z.string().refine((v) => { if (!v) return false; return String(v) .split(MULTIPLE_INPUT_SEPARATOR) .every((e) => !!e.trim()); - }, t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.placeholder")), + }, t("workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.placeholder")), resourceRegions: z .string() .nullish() diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index a2f61aac..56e9bed2 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -230,13 +230,13 @@ "workflow_node.deploy.form.aliyun_alb_snidomain.label": "Alibaba Cloud ALB SNI domain (Optional)", "workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "Please enter Alibaba Cloud ALB SNI domain name", "workflow_node.deploy.form.aliyun_alb_snidomain.help": "", + "workflow_node.deploy.form.aliyun_apigw_region.label": "Alibaba Cloud region", + "workflow_node.deploy.form.aliyun_apigw_region.placeholder": "Please enter Alibaba Cloud API gateway region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_apigw_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/api-gateway/cloud-native-api-gateway/product-overview/regions", "workflow_node.deploy.form.aliyun_apigw_service_type.label": "Alibaba Cloud API gateway type", "workflow_node.deploy.form.aliyun_apigw_service_type.placeholder": "Please select Alibaba Cloud API gateway type", "workflow_node.deploy.form.aliyun_apigw_service_type.option.cloudnative.label": "Cloud-native API gateway", "workflow_node.deploy.form.aliyun_apigw_service_type.option.traditional.label": "Traditional API gateway", - "workflow_node.deploy.form.aliyun_apigw_region.label": "Alibaba Cloud region", - "workflow_node.deploy.form.aliyun_apigw_region.placeholder": "Please enter Alibaba Cloud API gateway region (e.g. cn-hangzhou)", - "workflow_node.deploy.form.aliyun_apigw_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/api-gateway/cloud-native-api-gateway/product-overview/regions", "workflow_node.deploy.form.aliyun_apigw_gateway_id.label": "Alibaba Cloud API gateway ID", "workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder": "Please enter Alibaba Cloud API gateway ID", "workflow_node.deploy.form.aliyun_apigw_gateway_id.tooltip": "For more information, see https://apigw.console.aliyun.com", @@ -358,11 +358,21 @@ "workflow_node.deploy.form.aliyun_waf_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint", "workflow_node.deploy.form.aliyun_waf_service_version.label": "Alibaba Cloud WAF version", "workflow_node.deploy.form.aliyun_waf_service_version.placeholder": "Please select Alibaba Cloud WAF version", + "workflow_node.deploy.form.aliyun_waf_service_type.label": "Alibaba Cloud WAF access type", + "workflow_node.deploy.form.aliyun_waf_service_type.placeholder": "Please select Alibaba Cloud WAF access type", + "workflow_node.deploy.form.aliyun_waf_service_type.option.cloudresource.label": "Cloud product access", + "workflow_node.deploy.form.aliyun_waf_service_type.option.cname.label": "CNAME access", "workflow_node.deploy.form.aliyun_waf_instance_id.label": "Alibaba Cloud WAF instance ID", "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "Please enter Alibaba Cloud WAF instance ID", "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "For more information, see https://waf.console.aliyun.com", - "workflow_node.deploy.form.aliyun_waf_domain.label": "Alibaba Cloud WAF domain (Optional)", - "workflow_node.deploy.form.aliyun_waf_domain.placeholder": "Please enter Alibaba Cloud WAF domain name", + "workflow_node.deploy.form.aliyun_waf_resource_product.label": "Alibaba Cloud WAF accessed resource product", + "workflow_node.deploy.form.aliyun_waf_resource_product.placeholder": "Please enter Alibaba Cloud WAF accessed resource product", + "workflow_node.deploy.form.aliyun_waf_resource_id.label": "Alibaba Cloud WAF accessed resource ID", + "workflow_node.deploy.form.aliyun_waf_resource_id.placeholder": "Please enter Alibaba Cloud WAF accessed resource ID", + "workflow_node.deploy.form.aliyun_waf_resource_port.label": "Alibaba Cloud WAF accessed resource port", + "workflow_node.deploy.form.aliyun_waf_resource_port.placeholder": "Please enter Alibaba Cloud WAF accessed resource port", + "workflow_node.deploy.form.aliyun_waf_domain.label": "Alibaba Cloud WAF SNI domain (Optional)", + "workflow_node.deploy.form.aliyun_waf_domain.placeholder": "Please enter Alibaba Cloud WAF SNI domain name", "workflow_node.deploy.form.aliyun_waf_domain.help": "", "workflow_node.deploy.form.apisix_resource_type.label": "Resource type", "workflow_node.deploy.form.apisix_resource_type.placeholder": "Please select resource type", @@ -845,9 +855,9 @@ "workflow_node.deploy.form.tencentcloud_ssldeploy_region.label": "Tencent Cloud region", "workflow_node.deploy.form.tencentcloud_ssldeploy_region.placeholder": "Please enter Tencent Cloud service region (e.g. ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_ssldeploy_region.tooltip": "For more information, see https://www.tencentcloud.com/document/product/1007/36573", - "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.label": "Tencent Cloud resource type", - "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.placeholder": "Please enter Tencent Cloud resource type", - "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.tooltip": "For more information, see https://cloud.tencent.com/document/product/400/91667", + "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.label": "Tencent Cloud resource product", + "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.placeholder": "Please enter Tencent Cloud resource product", + "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.tooltip": "For more information, see https://cloud.tencent.com/document/product/400/91667", "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.label": "Tencent Cloud resource IDs", "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.placeholder": "Please enter Tencent Cloud resource IDs (separated by semicolons)", "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.errmsg.invalid": "Please enter a valid Tencent Cloud resource ID", @@ -862,12 +872,12 @@ "workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.label": "Tencent Cloud SSL certificate ID", "workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.placeholder": "Please enter Tencent Cloud SSL certificate ID", "workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.tooltip": "For more information, see https://console.cloud.tencent.com/certoverview", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.label": "Tencent Cloud resource types", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.placeholder": "Please enter Tencent Cloud resource types (separated by semicolons)", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.help": "Notes: Multi-values should be separated by semicolons.", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.tooltip": "For more information, see https://www.tencentcloud.com/document/product/1007/57981 or https://www.tencentcloud.com/document/product/1007/70503", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.title": "Change Tencent Cloud resource types", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.placeholder": "Please enter Tencent Cloud resource type", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.label": "Tencent Cloud resource products", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.placeholder": "Please enter Tencent Cloud resource products (separated by semicolons)", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.help": "Notes: Multi-values should be separated by semicolons.", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.tooltip": "For more information, see https://www.tencentcloud.com/document/product/1007/57981 or https://www.tencentcloud.com/document/product/1007/70503", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.title": "Change Tencent Cloud resource products", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.placeholder": "Please enter Tencent Cloud resource product", "workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.label": "Tencent Cloud resource regions (Optional)", "workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.placeholder": "Please enter Tencent Cloud resource regions (separated by semicolons)", "workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.help": "Notes: Multi-values should be separated by semicolons.", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 1ebc1c07..00504453 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -229,13 +229,13 @@ "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.help": "提示:不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.aliyun_apigw_region.label": "阿里云服务地域", + "workflow_node.deploy.form.aliyun_apigw_region.placeholder": "请输入阿里云 API 网关地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_apigw_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/product-overview/regions", "workflow_node.deploy.form.aliyun_apigw_service_type.label": "阿里云 API 网关服务类型", "workflow_node.deploy.form.aliyun_apigw_service_type.placeholder": "请选择阿里云 API 网关服务类型", "workflow_node.deploy.form.aliyun_apigw_service_type.option.cloudnative.label": "云原生 API 网关", "workflow_node.deploy.form.aliyun_apigw_service_type.option.traditional.label": "原 API 网关", - "workflow_node.deploy.form.aliyun_apigw_region.label": "阿里云服务地域", - "workflow_node.deploy.form.aliyun_apigw_region.placeholder": "请输入阿里云 API 网关地域(例如:cn-hangzhou)", - "workflow_node.deploy.form.aliyun_apigw_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/product-overview/regions", "workflow_node.deploy.form.aliyun_apigw_gateway_id.label": "阿里云 API 网关 ID", "workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder": "请输入阿里云 API 网关 ID", "workflow_node.deploy.form.aliyun_apigw_gateway_id.tooltip": "这是什么?请参阅 https://apigw.console.aliyun.com", @@ -357,11 +357,21 @@ "workflow_node.deploy.form.aliyun_waf_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint", "workflow_node.deploy.form.aliyun_waf_service_version.label": "阿里云 WAF 服务版本", "workflow_node.deploy.form.aliyun_waf_service_version.placeholder": "请选择阿里云 WAF 服务版本", + "workflow_node.deploy.form.aliyun_waf_service_type.label": "阿里云 WAF 服务接入方式", + "workflow_node.deploy.form.aliyun_waf_service_type.placeholder": "请选择阿里云 WAF 服务接入方式", + "workflow_node.deploy.form.aliyun_waf_service_type.option.cloudresource.label": "云产品接入", + "workflow_node.deploy.form.aliyun_waf_service_type.option.cname.label": "CNAME 接入", "workflow_node.deploy.form.aliyun_waf_instance_id.label": "阿里云 WAF 实例 ID", "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "请输入阿里云 WAF 实例 ID", - "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 https://waf.console.aliyun.com
仅支持 CNAME 接入。", - "workflow_node.deploy.form.aliyun_waf_domain.label": "阿里云 WAF 接入域名(可选)", - "workflow_node.deploy.form.aliyun_waf_domain.placeholder": "请输入阿里云 WAF 接入域名", + "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 https://waf.console.aliyun.com", + "workflow_node.deploy.form.aliyun_waf_resource_product.label": "阿里云 WAF 云产品接入资源类型", + "workflow_node.deploy.form.aliyun_waf_resource_product.placeholder": "请选择 WAF 云产品接入资源类型", + "workflow_node.deploy.form.aliyun_waf_resource_id.label": "阿里云 WAF 云产品接入资源 ID", + "workflow_node.deploy.form.aliyun_waf_resource_id.placeholder": "请选择阿里云 WAF 云产品接入资源 ID", + "workflow_node.deploy.form.aliyun_waf_resource_port.label": "阿里云 WAF 云产品接入端口", + "workflow_node.deploy.form.aliyun_waf_resource_port.placeholder": "请选择阿里云 WAF 云产品接入端口", + "workflow_node.deploy.form.aliyun_waf_domain.label": "阿里云 WAF 扩展域名(可选)", + "workflow_node.deploy.form.aliyun_waf_domain.placeholder": "请输入阿里云 WAF 扩展域名", "workflow_node.deploy.form.aliyun_waf_domain.help": "提示:不填写时,将替换实例的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.apisix_resource_type.label": "证书部署方式", "workflow_node.deploy.form.apisix_resource_type.placeholder": "请选择证书部署方式", @@ -843,9 +853,9 @@ "workflow_node.deploy.form.tencentcloud_ssldeploy_region.label": "腾讯云服务地域", "workflow_node.deploy.form.tencentcloud_ssldeploy_region.placeholder": "请输入腾讯云云产品服务地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_ssldeploy_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659", - "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.label": "腾讯云云产品资源类型", - "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.placeholder": "请输入腾讯云产品资源类型", - "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_type.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91667", + "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.label": "腾讯云云产品资源类型", + "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.placeholder": "请输入腾讯云产品资源类型", + "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_product.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91667", "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.label": "腾讯云云产品资源 ID", "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.placeholder": "请输入腾讯云云产品资源 ID(多个值请用半角分号隔开)", "workflow_node.deploy.form.tencentcloud_ssldeploy_resource_ids.errmsg.invalid": "请输入正确的腾讯云云产品资源 ID", @@ -860,12 +870,12 @@ "workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.label": "腾讯云原证书 ID", "workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.placeholder": "请输入腾讯云原证书 ID", "workflow_node.deploy.form.tencentcloud_sslupdate_certificate_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/certoverview", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.label": "腾讯云云产品资源类型", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.placeholder": "请输入腾讯云云产品资源类型(多个值请用半角分号隔开)", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.help": "提示:多个类型请用半角分号隔开。", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91649https://cloud.tencent.com/document/product/400/119791
注意,这两个接口的所支持的云产品资源类型有所不同,具体请查看腾讯云官方文档。", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.title": "修改腾讯云云产品资源类型", - "workflow_node.deploy.form.tencentcloud_sslupdate_resource_types.multiple_input_modal.placeholder": "请输入腾讯云云产品资源类型", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.label": "腾讯云云产品资源类型", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.placeholder": "请输入腾讯云云产品资源类型(多个值请用半角分号隔开)", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.help": "提示:多个类型请用半角分号隔开。", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91649https://cloud.tencent.com/document/product/400/119791
注意,这两个接口的所支持的云产品资源类型有所不同,具体请查看腾讯云官方文档。", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.title": "修改腾讯云云产品资源类型", + "workflow_node.deploy.form.tencentcloud_sslupdate_resource_products.multiple_input_modal.placeholder": "请输入腾讯云云产品资源类型", "workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.label": "腾讯云云产品部署地域(可选)", "workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.placeholder": "请输入腾讯云云产品部署地域(多个值请用半角分号隔开)", "workflow_node.deploy.form.tencentcloud_sslupdate_resource_regions.help": "提示:多个地域请用半角分号隔开。",