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 ( <> +