diff --git a/go.mod b/go.mod index b485b5da..41592f78 100644 --- a/go.mod +++ b/go.mod @@ -73,6 +73,7 @@ require ( github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.3.101 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.3.105 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.3.112 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse v1.3.107 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.3.112 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.3.111 github.com/ucloud/ucloud-sdk-go v0.22.77 diff --git a/go.sum b/go.sum index 70cc2497..8589b2c4 100644 --- a/go.sum +++ b/go.sum @@ -903,6 +903,7 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.90/go.mod h github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.101/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.103/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.105/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.107/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.111/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.112 h1:PNCN4HXHVAfG87Msvelk7Ks5MhotAXk2vqPVvfy1Epw= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.112/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= @@ -916,6 +917,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.3.105 h1:rzbhp0b github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.3.105/go.mod h1:KScd4O0ZJum7PsWPoJY1wbKnK1zRCAq7RH3PBRmiYJI= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.3.112 h1:VPiTJ5n49TS2nmCF1LDZ/T2jSRpsv1ZJnddTkzrvm1I= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.3.112/go.mod h1:FGEhdHxUk6wOSNpDvr+wdL34Lu6FunNDCQTyK4YqwUw= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse v1.3.107 h1:doa/f0RtjMuoCpQo5P/Ni3EPOEmcn7MCkCRMnULWqHo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse v1.3.107/go.mod h1:uTigeKJi8/YABmv8LeucOcpK/ZAK5htJaCia8L3ol6E= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.3.112 h1:vxOJOerDOVMvjFh+X3ZMrbb8/htfyxDSKjGkmtD+2jM= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.3.112/go.mod h1:zCS+AwYqW2r0RBeCWHh9dmydCmP6LSew6y9Uyxsedy0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.3.111 h1:CKRs1TWBVinSLGZ2J0OVtfzoqoWMizrJ6IdYo6QEfow= diff --git a/internal/certmgmt/deployers/sp_tencentcloud_tse.go b/internal/certmgmt/deployers/sp_tencentcloud_tse.go new file mode 100644 index 00000000..3b8d0082 --- /dev/null +++ b/internal/certmgmt/deployers/sp_tencentcloud_tse.go @@ -0,0 +1,35 @@ +package deployers + +import ( + "fmt" + "strings" + + "github.com/samber/lo" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core" + dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/tencentcloud-tse" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + Registries.MustRegister(domain.DeploymentProviderTypeTencentCloudTSE, func(options *ProviderFactoryOptions) (core.Deployer, error) { + credentials := domain.AccessConfigForTencentCloud{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{ + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + ProjectId: credentials.ProjectId, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + ServiceType: xmaps.GetString(options.ProviderExtendedConfig, "serviceType"), + GatewayId: xmaps.GetString(options.ProviderExtendedConfig, "gatewayId"), + Domains: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "domains"), ";"), func(s string, _ int) bool { return s != "" }), + CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"), + }) + return provider, err + }) +} diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 491e2d28..c3fa00a7 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -419,6 +419,7 @@ const ( DeploymentProviderTypeTencentCloudSSL = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ssl") DeploymentProviderTypeTencentCloudSSLDeploy = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ssldeploy") DeploymentProviderTypeTencentCloudSSLUpdate = DeploymentProviderType(AccessProviderTypeTencentCloud + "-sslupdate") + DeploymentProviderTypeTencentCloudTSE = DeploymentProviderType(AccessProviderTypeTencentCloud + "-tse") DeploymentProviderTypeTencentCloudVOD = DeploymentProviderType(AccessProviderTypeTencentCloud + "-vod") DeploymentProviderTypeTencentCloudWAF = DeploymentProviderType(AccessProviderTypeTencentCloud + "-waf") DeploymentProviderTypeUCloudUALB = DeploymentProviderType(AccessProviderTypeUCloud + "-ualb") diff --git a/pkg/core/certmgr/providers/tencentcloud-tse/consts.go b/pkg/core/certmgr/providers/tencentcloud-tse/consts.go new file mode 100644 index 00000000..cb37afa6 --- /dev/null +++ b/pkg/core/certmgr/providers/tencentcloud-tse/consts.go @@ -0,0 +1,6 @@ +package tencentcloudtse + +const ( + // 服务类型:云原生网关。 + SERVICE_TYPE_CLOUDNATIVE = "cloudnative" +) diff --git a/pkg/core/certmgr/providers/tencentcloud-tse/tencentcloud_tse.go b/pkg/core/certmgr/providers/tencentcloud-tse/tencentcloud_tse.go new file mode 100644 index 00000000..030ccba9 --- /dev/null +++ b/pkg/core/certmgr/providers/tencentcloud-tse/tencentcloud_tse.go @@ -0,0 +1,239 @@ +package tencentcloudtse + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/samber/lo" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + + tcssl "github.com/certimate-go/certimate/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" + tctse "github.com/certimate-go/certimate/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207" + + "github.com/certimate-go/certimate/pkg/core" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" +) + +type ( + Provider = core.Certmgr + UploadResult = core.CertmgrUploadResult + ReplaceResult = core.CertmgrReplaceResult +) + +type CertmgrConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` + // 腾讯云项目 ID。 + ProjectId int64 `json:"projectId,omitempty"` + // 腾讯云接口端点。 + Endpoint string `json:"endpoint,omitempty"` + // 腾讯云地域。 + Region string `json:"region"` + // 服务类型。 + ServiceType string `json:"serviceType"` + // 云原生网关 ID。 + // 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时必填。 + GatewayId string `json:"gatewayId,omitempty"` + // 云原生网关绑定的域名。 + // 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时选填。 + // 零值时根据证书内容自动识别。 + Domains []string `json:"domains,omitempty"` +} + +type Certmgr struct { + config *CertmgrConfig + logger *slog.Logger + sdkClient *wSDKClients +} + +var _ Provider = (*Certmgr)(nil) + +type wSDKClients struct { + SSL *tcssl.Client + TSE *tctse.Client +} + +func NewCertmgr(config *CertmgrConfig) (*Certmgr, error) { + if config == nil { + return nil, fmt.Errorf("the configuration of the certmgr provider is nil") + } + + client, err := createSDKClients(config.SecretId, config.SecretKey, config.Endpoint, config.Region) + if err != nil { + return nil, fmt.Errorf("could not create client: %w", err) + } + + return &Certmgr{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (c *Certmgr) SetLogger(logger *slog.Logger) { + if logger == nil { + c.logger = slog.New(slog.DiscardHandler) + } else { + c.logger = logger + } +} + +func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*UploadResult, error) { + switch c.config.ServiceType { + case SERVICE_TYPE_CLOUDNATIVE: + return c.uploadToCloudNative(ctx, certPEM, privkeyPEM) + + default: + return nil, fmt.Errorf("unsupported service type '%s'", c.config.ServiceType) + } +} + +func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*ReplaceResult, error) { + switch c.config.ServiceType { + case SERVICE_TYPE_CLOUDNATIVE: + return c.replaceToCloudNative(ctx, certIdOrName, certPEM, privkeyPEM) + + default: + return nil, fmt.Errorf("unsupported service type '%s'", c.config.ServiceType) + } +} + +func (c *Certmgr) uploadToCloudNative(ctx context.Context, certPEM, privkeyPEM string) (*UploadResult, error) { + // 解析证书内容 + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + // 先上传证书到 SSL + // REF: https://cloud.tencent.com/document/api/400/41665 + uploadCertificateReq := tcssl.NewUploadCertificateRequest() + uploadCertificateReq.ProjectId = lo.EmptyableToPtr(uint64(c.config.ProjectId)) + uploadCertificateReq.CertificatePublicKey = common.StringPtr(certPEM) + uploadCertificateReq.CertificatePrivateKey = common.StringPtr(privkeyPEM) + uploadCertificateReq.Repeatable = common.BoolPtr(false) + uploadCertificateResp, err := c.sdkClient.SSL.UploadCertificateWithContext(ctx, uploadCertificateReq) + c.logger.Debug("sdk request 'ssl.UploadCertificate'", slog.Any("request", uploadCertificateReq), slog.Any("response", uploadCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'ssl.UploadCertificate': %w", err) + } + + // 查询云原生网关证书列表,避免重复上传 + // REF: https://cloud.tencent.com/document/api/1364/98588 + describeCloudNativeAPIGatewayCertificatesOffset := 0 + describeCloudNativeAPIGatewayCertificatesLimit := 100 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeCloudNativeAPIGatewayCertificatesReq := tctse.NewDescribeCloudNativeAPIGatewayCertificatesRequest() + describeCloudNativeAPIGatewayCertificatesReq.GatewayId = common.StringPtr(c.config.GatewayId) + describeCloudNativeAPIGatewayCertificatesReq.Offset = common.Int64Ptr(int64(describeCloudNativeAPIGatewayCertificatesOffset)) + describeCloudNativeAPIGatewayCertificatesReq.Limit = common.Int64Ptr(int64(describeCloudNativeAPIGatewayCertificatesLimit)) + describeCloudNativeAPIGatewayCertificatesResp, err := c.sdkClient.TSE.DescribeCloudNativeAPIGatewayCertificatesWithContext(ctx, describeCloudNativeAPIGatewayCertificatesReq) + c.logger.Debug("sdk request 'tse.DescribeCloudNativeAPIGatewayCertificates'", slog.Any("request", describeCloudNativeAPIGatewayCertificatesReq), slog.Any("response", describeCloudNativeAPIGatewayCertificatesResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'tse.DescribeCloudNativeAPIGatewayCertificates': %w", err) + } + + for _, certItem := range describeCloudNativeAPIGatewayCertificatesResp.Response.Result.CertificatesList { + if lo.FromPtr(uploadCertificateResp.Response.CertificateId) == lo.FromPtr(certItem.CertId) || + xcert.EqualCertificatesFromPEM(certPEM, lo.FromPtr(certItem.Crt)) { + // 如果已存在相同证书,直接返回 + c.logger.Info("ssl certificate already exists") + return &UploadResult{ + CertId: lo.FromPtr(certItem.CertId), + CertName: lo.FromPtr(certItem.Name), + }, nil + } + } + + if len(describeCloudNativeAPIGatewayCertificatesResp.Response.Result.CertificatesList) < describeCloudNativeAPIGatewayCertificatesLimit { + break + } + + describeCloudNativeAPIGatewayCertificatesOffset += describeCloudNativeAPIGatewayCertificatesLimit + } + + // 生成新证书名(需符合腾讯云命名规则) + certName := fmt.Sprintf("certimate_%d", time.Now().UnixMilli()) + + // 创建云原生网关证书 + // REF: https://cloud.tencent.com/document/api/1364/98591 + createCloudNativeAPIGatewayCertificateReq := tctse.NewCreateCloudNativeAPIGatewayCertificateRequest() + createCloudNativeAPIGatewayCertificateReq.GatewayId = common.StringPtr(c.config.GatewayId) + createCloudNativeAPIGatewayCertificateReq.Name = common.StringPtr(certName) + createCloudNativeAPIGatewayCertificateReq.CertId = uploadCertificateResp.Response.CertificateId + createCloudNativeAPIGatewayCertificateReq.BindDomains = common.StringPtrs(lo.Ternary(len(c.config.Domains) != 0, c.config.Domains, certX509.DNSNames)) + createCloudNativeAPIGatewayCertificateResp, err := c.sdkClient.TSE.CreateCloudNativeAPIGatewayCertificateWithContext(ctx, createCloudNativeAPIGatewayCertificateReq) + c.logger.Debug("sdk request 'tse.CreateCloudNativeAPIGatewayCertificate'", slog.Any("request", createCloudNativeAPIGatewayCertificateReq), slog.Any("response", createCloudNativeAPIGatewayCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'tse.CreateCloudNativeAPIGatewayCertificate': %w", err) + } + + return &UploadResult{ + CertId: lo.FromPtr(createCloudNativeAPIGatewayCertificateResp.Response.Result.Id), + CertName: certName, + }, nil +} + +func (c *Certmgr) replaceToCloudNative(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*ReplaceResult, error) { + // 更新云原生网关证书 + // REF: https://cloud.tencent.com/document/api/1364/100199 + modifyCloudNativeAPIGatewayCertificateReq := tctse.NewModifyCloudNativeAPIGatewayCertificateRequest() + modifyCloudNativeAPIGatewayCertificateReq.GatewayId = common.StringPtr(c.config.GatewayId) + modifyCloudNativeAPIGatewayCertificateReq.Id = common.StringPtr(certIdOrName) + modifyCloudNativeAPIGatewayCertificateReq.Crt = common.StringPtr(certPEM) + modifyCloudNativeAPIGatewayCertificateReq.Key = common.StringPtr(privkeyPEM) + modifyCloudNativeAPIGatewayCertificateReq.CertSource = common.StringPtr("native") + modifyCloudNativeAPIGatewayCertificateResp, err := c.sdkClient.TSE.ModifyCloudNativeAPIGatewayCertificateWithContext(ctx, modifyCloudNativeAPIGatewayCertificateReq) + c.logger.Debug("sdk request 'tse.ModifyCloudNativeAPIGatewayCertificate'", slog.Any("request", modifyCloudNativeAPIGatewayCertificateReq), slog.Any("response", modifyCloudNativeAPIGatewayCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'tse.ModifyCloudNativeAPIGatewayCertificate': %w", err) + } + + return &ReplaceResult{}, nil +} + +func createSDKClients(secretId, secretKey, endpoint, region string) (*wSDKClients, error) { + wsdk := &wSDKClients{} + + { + credential := common.NewCredential(secretId, secretKey) + + cpf := profile.NewClientProfile() + + client, err := tcssl.NewClient(credential, "", cpf) + if err != nil { + return nil, err + } + + wsdk.SSL = client + } + + { + credential := common.NewCredential(secretId, secretKey) + + cpf := profile.NewClientProfile() + if endpoint != "" { + cpf.HttpProfile.Endpoint = endpoint + } + + client, err := tctse.NewClient(credential, region, cpf) + if err != nil { + return nil, err + } + + wsdk.TSE = client + } + + return wsdk, nil +} diff --git a/pkg/core/certmgr/providers/tencentcloud-tse/tencentcloud_tse_test.go b/pkg/core/certmgr/providers/tencentcloud-tse/tencentcloud_tse_test.go new file mode 100644 index 00000000..fd477105 --- /dev/null +++ b/pkg/core/certmgr/providers/tencentcloud-tse/tencentcloud_tse_test.go @@ -0,0 +1,61 @@ +package tencentcloudtse_test + +import ( + "testing" + + "github.com/certimate-go/certimate/pkg/core/certmgr/internal/tester" + impl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/tencentcloud-tse" +) + +var ( + fp = tester.Args("TENCENTCLOUDTSE_") + fTestCertPath string + fTestKeyPath string + fSecretId string + fSecretKey string + fRegion string + fServiceType string + fGatewayId string +) + +func init() { + fp.DefineString(&fTestCertPath, "TESTCERTPATH") + fp.DefineString(&fTestKeyPath, "TESTKEYPATH") + fp.DefineString(&fSecretId, "SECRETID") + fp.DefineString(&fSecretKey, "SECRETKEY") + fp.DefineString(&fRegion, "REGION") + fp.DefineString(&fServiceType, "SERVICETYPE") + fp.DefineString(&fGatewayId, "GATEWAYID") +} + +/* +Shell command to run this test: + + go test -v ./tencentcloud_tse_test.go -args \ + --TENCENTCLOUDTSE_TESTCERTPATH="/path/to/your-test-cert.pem" \ + --TENCENTCLOUDTSE_TESTKEYPATH="/path/to/your-test-key.pem" \ + --TENCENTCLOUDTSE_SECRETID="your-secret-id" \ + --TENCENTCLOUDTSE_SECRETKEY="your-secret-key" \ + --TENCENTCLOUDTSE_REGION="ap-guangzhou" \ + --TENCENTCLOUDTSE_SERVICETYPE="cloudnative" \ + --TENCENTCLOUDTSE_GATEWAYID="your-gateway-id" +*/ +func TestProvider(t *testing.T) { + fp.Parse() + + t.Run("Upload", func(t *testing.T) { + provider, err := impl.NewCertmgr(&impl.CertmgrConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + Region: fRegion, + ServiceType: fServiceType, + GatewayId: fGatewayId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + tester.TestUpload(t, provider, tester.TestUploadArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath}) + }) +} diff --git a/pkg/core/deployer/providers/tencentcloud-tse/consts.go b/pkg/core/deployer/providers/tencentcloud-tse/consts.go new file mode 100644 index 00000000..3ff2915c --- /dev/null +++ b/pkg/core/deployer/providers/tencentcloud-tse/consts.go @@ -0,0 +1,10 @@ +package tencentcloudtse + +import ( + tse "github.com/certimate-go/certimate/pkg/core/certmgr/providers/tencentcloud-tse" +) + +const ( + // 服务类型:云原生网关。 + SERVICE_TYPE_CLOUDNATIVE = tse.SERVICE_TYPE_CLOUDNATIVE +) diff --git a/pkg/core/deployer/providers/tencentcloud-tse/tencentcloud_tse.go b/pkg/core/deployer/providers/tencentcloud-tse/tencentcloud_tse.go new file mode 100644 index 00000000..984aaa59 --- /dev/null +++ b/pkg/core/deployer/providers/tencentcloud-tse/tencentcloud_tse.go @@ -0,0 +1,107 @@ +package tencentcloudtse + +import ( + "context" + "fmt" + "log/slog" + + "github.com/certimate-go/certimate/pkg/core" + cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/tencentcloud-tse" +) + +type ( + Provider = core.Deployer + DeployResult = core.DeployerDeployResult +) + +type DeployerConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` + // 腾讯云项目 ID。 + ProjectId int64 `json:"projectId,omitempty"` + // 腾讯云接口端点。 + Endpoint string `json:"endpoint,omitempty"` + // 腾讯云地域。 + Region string `json:"region"` + // 服务类型。 + ServiceType string `json:"serviceType"` + // 云原生网关 ID。 + // 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时必填。 + GatewayId string `json:"gatewayId,omitempty"` + // 云原生网关绑定的域名。 + // 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时选填。 + // 零值时根据证书内容自动识别。 + Domains []string `json:"domains,omitempty"` + // 云原生网关证书 ID。 + // 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时选填。 + // 零值时表示新建证书;否则表示更新证书。 + CertificateId string `json:"certificateId,omitempty"` +} + +type Deployer struct { + config *DeployerConfig + logger *slog.Logger + sdkCertmgr core.Certmgr +} + +var _ Provider = (*Deployer)(nil) + +func NewDeployer(config *DeployerConfig) (*Deployer, error) { + if config == nil { + return nil, fmt.Errorf("the configuration of the deployer provider is nil") + } + + pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{ + SecretId: config.SecretId, + SecretKey: config.SecretKey, + ProjectId: config.ProjectId, + Endpoint: config.Endpoint, + Region: config.Region, + ServiceType: config.ServiceType, + GatewayId: config.GatewayId, + Domains: config.Domains, + }) + if err != nil { + return nil, fmt.Errorf("could not create certmgr: %w", err) + } + + return &Deployer{ + config: config, + logger: slog.Default(), + sdkCertmgr: pcertmgr, + }, nil +} + +func (d *Deployer) SetLogger(logger *slog.Logger) { + if logger == nil { + d.logger = slog.New(slog.DiscardHandler) + } else { + d.logger = logger + } + + d.sdkCertmgr.SetLogger(logger) +} + +func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) { + if d.config.CertificateId == "" { + // 上传证书 + upres, err := d.sdkCertmgr.Upload(ctx, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to upload certificate file: %w", err) + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + } else { + // 替换证书 + rplres, err := d.sdkCertmgr.Replace(ctx, d.config.CertificateId, certPEM, privkeyPEM) + if err != nil { + return nil, fmt.Errorf("failed to replace certificate file: %w", err) + } else { + d.logger.Info("ssl certificate replaced", slog.Any("result", rplres)) + } + } + + return &DeployResult{}, nil +} diff --git a/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207/client.go b/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207/client.go new file mode 100644 index 00000000..a80eb240 --- /dev/null +++ b/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207/client.go @@ -0,0 +1,99 @@ +package v20201207 + +import ( + "context" + "errors" + + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + tse "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207" +) + +const APIVersion = tse.APIVersion + +type Client struct { + common.Client +} + +func NewClient(credential common.CredentialIface, region string, clientProfile *profile.ClientProfile) (client *Client, err error) { + client = &Client{} + client.Init(region). + WithCredential(credential). + WithProfile(clientProfile) + return +} + +func NewCreateCloudNativeAPIGatewayCertificateRequest() (request *CreateCloudNativeAPIGatewayCertificateRequest) { + return tse.NewCreateCloudNativeAPIGatewayCertificateRequest() +} + +func NewCreateCloudNativeAPIGatewayCertificateResponse() (response *CreateCloudNativeAPIGatewayCertificateResponse) { + return tse.NewCreateCloudNativeAPIGatewayCertificateResponse() +} + +func (c *Client) CreateCloudNativeAPIGatewayCertificateWithContext(ctx context.Context, request *CreateCloudNativeAPIGatewayCertificateRequest) (response *CreateCloudNativeAPIGatewayCertificateResponse, err error) { + if request == nil { + request = NewCreateCloudNativeAPIGatewayCertificateRequest() + } + c.InitBaseRequest(&request.BaseRequest, "tse", APIVersion, "CreateCloudNativeAPIGatewayCertificate") + + if c.GetCredential() == nil { + return nil, errors.New("CreateCloudNativeAPIGatewayCertificate require credential") + } + + request.SetContext(ctx) + + response = NewCreateCloudNativeAPIGatewayCertificateResponse() + err = c.Send(request, response) + return +} + +func NewDescribeCloudNativeAPIGatewayCertificatesRequest() (request *DescribeCloudNativeAPIGatewayCertificatesRequest) { + return tse.NewDescribeCloudNativeAPIGatewayCertificatesRequest() +} + +func NewDescribeCloudNativeAPIGatewayCertificatesResponse() (response *DescribeCloudNativeAPIGatewayCertificatesResponse) { + return tse.NewDescribeCloudNativeAPIGatewayCertificatesResponse() +} + +func (c *Client) DescribeCloudNativeAPIGatewayCertificatesWithContext(ctx context.Context, request *DescribeCloudNativeAPIGatewayCertificatesRequest) (response *DescribeCloudNativeAPIGatewayCertificatesResponse, err error) { + if request == nil { + request = NewDescribeCloudNativeAPIGatewayCertificatesRequest() + } + c.InitBaseRequest(&request.BaseRequest, "tse", APIVersion, "DescribeCloudNativeAPIGatewayCertificates") + + if c.GetCredential() == nil { + return nil, errors.New("DescribeCloudNativeAPIGatewayCertificates require credential") + } + + request.SetContext(ctx) + + response = NewDescribeCloudNativeAPIGatewayCertificatesResponse() + err = c.Send(request, response) + return +} + +func NewModifyCloudNativeAPIGatewayCertificateRequest() (request *ModifyCloudNativeAPIGatewayCertificateRequest) { + return tse.NewModifyCloudNativeAPIGatewayCertificateRequest() +} + +func NewModifyCloudNativeAPIGatewayCertificateResponse() (response *ModifyCloudNativeAPIGatewayCertificateResponse) { + return tse.NewModifyCloudNativeAPIGatewayCertificateResponse() +} + +func (c *Client) ModifyCloudNativeAPIGatewayCertificateWithContext(ctx context.Context, request *ModifyCloudNativeAPIGatewayCertificateRequest) (response *ModifyCloudNativeAPIGatewayCertificateResponse, err error) { + if request == nil { + request = NewModifyCloudNativeAPIGatewayCertificateRequest() + } + c.InitBaseRequest(&request.BaseRequest, "tse", APIVersion, "ModifyCloudNativeAPIGatewayCertificate") + + if c.GetCredential() == nil { + return nil, errors.New("ModifyCloudNativeAPIGatewayCertificate require credential") + } + + request.SetContext(ctx) + + response = NewModifyCloudNativeAPIGatewayCertificateResponse() + err = c.Send(request, response) + return +} diff --git a/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207/models.go b/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207/models.go new file mode 100644 index 00000000..129f9918 --- /dev/null +++ b/pkg/sdk3rd-trimmed/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207/models.go @@ -0,0 +1,17 @@ +package v20201207 + +import ( + tse "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse/v20201207" +) + +type CreateCloudNativeAPIGatewayCertificateRequest = tse.CreateCloudNativeAPIGatewayCertificateRequest + +type CreateCloudNativeAPIGatewayCertificateResponse = tse.CreateCloudNativeAPIGatewayCertificateResponse + +type DescribeCloudNativeAPIGatewayCertificatesRequest = tse.DescribeCloudNativeAPIGatewayCertificatesRequest + +type DescribeCloudNativeAPIGatewayCertificatesResponse = tse.DescribeCloudNativeAPIGatewayCertificatesResponse + +type ModifyCloudNativeAPIGatewayCertificateRequest = tse.ModifyCloudNativeAPIGatewayCertificateRequest + +type ModifyCloudNativeAPIGatewayCertificateResponse = tse.ModifyCloudNativeAPIGatewayCertificateResponse diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx index e2eb3b94..0a2e104c 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx @@ -108,6 +108,7 @@ import BizDeployNodeConfigFieldsProviderTencentCloudSCF from "./BizDeployNodeCon import BizDeployNodeConfigFieldsProviderTencentCloudSSL from "./BizDeployNodeConfigFieldsProviderTencentCloudSSL"; import BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy from "./BizDeployNodeConfigFieldsProviderTencentCloudSSLDeploy"; import BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate from "./BizDeployNodeConfigFieldsProviderTencentCloudSSLUpdate"; +import BizDeployNodeConfigFieldsProviderTencentCloudTSE from "./BizDeployNodeConfigFieldsProviderTencentCloudTSE"; import BizDeployNodeConfigFieldsProviderTencentCloudVOD from "./BizDeployNodeConfigFieldsProviderTencentCloudVOD"; import BizDeployNodeConfigFieldsProviderTencentCloudWAF from "./BizDeployNodeConfigFieldsProviderTencentCloudWAF"; import BizDeployNodeConfigFieldsProviderUCloudUALB from "./BizDeployNodeConfigFieldsProviderUCloudUALB"; @@ -248,6 +249,7 @@ const providerComponentMap: Partial { rules={[formRule]} > + + + + + + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + serviceType: SERVICE_TYPE_CLOUDNATIVE, + gatewayId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + serviceType: z.enum([SERVICE_TYPE_CLOUDNATIVE]), + endpoint: z.string().nullish(), + region: z.string().nonempty(), + gatewayId: z.string().nullish(), + domains: z.string().nullish(), + certificateId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.serviceType) { + switch (values.serviceType) { + case SERVICE_TYPE_CLOUDNATIVE: + { + const scGatewayId = z.string().nonempty(); + const spGatewayId = scGatewayId.safeParse(values.gatewayId); + if (!spGatewayId.success) { + ctx.addIssue({ + code: "custom", + message: z.treeifyError(spGatewayId.error).errors.join(), + path: ["gatewayId"], + }); + } + + const scDomains = z + .string() + .nonempty() + .refine((v) => v.split(MULTIPLE_INPUT_SEPARATOR).every((e) => isDomain(e, { allowWildcard: true })), t("common.errmsg.domain_invalid")); + const spDomains = scDomains.safeParse(values.domains); + if (!spDomains.success) { + ctx.addIssue({ + code: "custom", + message: z.treeifyError(spDomains.error).errors.join(), + path: ["domains"], + }); + } + } + break; + } + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFieldsProviderTencentCloudTSE, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 167b5322..6048c0f0 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -704,6 +704,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ TENCENTCLOUD_SSL: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ssl`, TENCENTCLOUD_SSL_DEPLOY: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ssldeploy`, TENCENTCLOUD_SSL_UPDATE: `${ACCESS_PROVIDERS.TENCENTCLOUD}-sslupdate`, + TENCENTCLOUD_TSE: `${ACCESS_PROVIDERS.TENCENTCLOUD}-tse`, TENCENTCLOUD_VOD: `${ACCESS_PROVIDERS.TENCENTCLOUD}-vod`, TENCENTCLOUD_WAF: `${ACCESS_PROVIDERS.TENCENTCLOUD}-waf`, UCLOUD_UALB: `${ACCESS_PROVIDERS.UCLOUD}-ualb`, @@ -797,6 +798,7 @@ export const deploymentProvidersMap: MapUpdateCertificateInstance; otherwise, it will invoke UploadUpdateCertificateInstance." }, + "tencentcloud_tse_endpoint": { + "label": "Tencent Cloud API endpoint (Optional)", + "placeholder": "Please enter Tencent Cloud TSE API endpoint (e.g. tse.intl.tencentcloudapi.com)", + "tooltip": "
  • tse.intl.tencentcloudapi.com for Tencent Cloud International
  • tse.tencentcloudapi.com for Tencent Cloud in China
" + }, + "tencentcloud_tse_region": { + "label": "Tencent Cloud region", + "placeholder": "Please enter Tencent Cloud TSE region (e.g. ap-guangzhou)", + "tooltip": "For more information, see https://www.tencentcloud.com/document/product/1290/79214" + }, + "tencentcloud_tse_service_type": { + "label": "Tencent Cloud TSE service type", + "placeholder": "Please select Tencent Cloud TSE service type", + "option": { + "cloudnative": { + "label": "Cloud-native API gateway" + } + } + }, + "tencentcloud_tse_gateway_id": { + "label": "Tencent Cloud TSE Cloud-native API gateway ID", + "placeholder": "Please enter Tencent Cloud TSE Cloud-native API gateway ID", + "tooltip": "For more information, see https://console.tencentcloud.com/rcc/governance" + }, + "tencentcloud_tse_domains": { + "label": "Tencent Cloud TSE Cloud-native API gateway binding domains", + "placeholder": "Please enter Tencent Cloud TSE Cloud-native API gateway binding domain names (separated by semicolons)", + "help": "Notes: Multiple domains should be separated by semicolons.", + "multiple_input_modal": { + "title": "Change Tencent Cloud TSE Cloud-native API gateway binding domains", + "placeholder": "Please enter Tencent Cloud TSE Cloud-native API gateway binding domain name" + } + }, + "tencentcloud_tse_certificate_id": { + "label": "Tencent Cloud TSE Cloud-native certificate ID", + "placeholder": "Please enter Tencent Cloud TSE Cloud-native certificate ID", + "help": "Notes: Leave it blank to import a new certificate; otherwise, to replace the existing one.", + "tooltip": "For more information, see https://console.tencentcloud.com/rcc/governance" + }, "tencentcloud_vod_endpoint": { "label": "Tencent Cloud API endpoint (Optional)", "placeholder": "Please enter Tencent Cloud VOD API endpoint (e.g. vod.intl.tencentcloudapi.com)", @@ -2650,7 +2689,7 @@ "help": "Notes: Multiple domains should be separated by semicolons.", "multiple_input_modal": { "title": "Change Wangsu Cloud CDN domains", - "placeholder": "Please enter Wangsu Cloud CDN domain" + "placeholder": "Please enter Wangsu Cloud CDN domain name" } }, "wangsu_cdnpro_environment": { diff --git a/ui/src/i18n/resources/zh/nls.provider.json b/ui/src/i18n/resources/zh/nls.provider.json index 99744b74..130acc57 100644 --- a/ui/src/i18n/resources/zh/nls.provider.json +++ b/ui/src/i18n/resources/zh/nls.provider.json @@ -243,6 +243,7 @@ "tencentcloud_ssl_deploy": "腾讯云 - 通过 SSL 证书服务创建部署任务", "tencentcloud_ssl_update": "腾讯云 - 通过 SSL 证书服务更新云资源证书", "tencentcloud_ssl_upload": "腾讯云 - 上传到 SSL 证书服务", + "tencentcloud_tse": "腾讯云 - 微服务引擎 TSE", "tencentcloud_vod": "腾讯云 - 云点播 VOD", "tencentcloud_waf": "腾讯云 - Web 应用防火墙 WAF", "todaynic": "时代互联", diff --git a/ui/src/i18n/resources/zh/nls.workflow.nodes.json b/ui/src/i18n/resources/zh/nls.workflow.nodes.json index 10b79fc3..e830a9d8 100644 --- a/ui/src/i18n/resources/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/resources/zh/nls.workflow.nodes.json @@ -952,7 +952,7 @@ "aws_acm_certificate_arn": { "label": "AWS ACM 证书 ARN(可选)", "placeholder": "请输入 AWS ACM 证书 ARN", - "help": "提示:不填写时,将导入为新证书;否则,将替换原证书。" + "help": "提示:不填写时,将上传新证书;否则,将替换原证书。" }, "aws_alb_region": { "label": "AWS 服务区域", @@ -2324,6 +2324,45 @@ "label": "是否更新原证书(即证书 ID 保持不变)", "tooltip": "不勾选时,将调用腾讯云 OpenAPI UpdateCertificateInstance 接口;否则,将调用腾讯云 OpenAPI UploadUpdateCertificateInstance 接口。" }, + "tencentcloud_tse_endpoint": { + "label": "腾讯云接口端点(可选)", + "placeholder": "请输入腾讯云 TSE 接口端点(例如:tse.tencentcloudapi.com)", + "tooltip": "这是什么?请参阅 https://cloud.tencent.com.cn/document/product/1364/54618
国际站用户请填写 tse.intl.tencentcloudapi.com。" + }, + "tencentcloud_tse_region": { + "label": "腾讯云服务地域", + "placeholder": "输入腾讯云 TSE 服务地域(例如:ap-guangzhou)", + "tooltip": "这是什么?请参阅 https://cloud.tencent.com.cn/document/product/1364/54618" + }, + "tencentcloud_tse_service_type": { + "label": "腾讯云 TSE 服务类型", + "placeholder": "请选择腾讯云 TSE 服务类型", + "option": { + "cloudnative": { + "label": "云原生网关" + } + } + }, + "tencentcloud_tse_gateway_id": { + "label": "腾讯云 TSE 云原生网关 ID", + "placeholder": "请输入腾讯云 TSE 云原生网关 ID", + "tooltip": "这是什么?请参阅 https://console.cloud.tencent.com.cn/rcc/governance" + }, + "tencentcloud_tse_domains": { + "label": "腾讯云 TSE 云原生网关绑定域名", + "placeholder": "输入腾讯云 TSE 云原生网关绑定域名(多个值请用半角分号隔开)", + "help": "提示:支持多个域名,以半角分号隔开。", + "multiple_input_modal": { + "title": "修改腾讯云 TSE 云原生网关绑定域名", + "placeholder": "请输入腾讯云 TSE 云原生网关绑定域名" + } + }, + "tencentcloud_tse_certificate_id": { + "label": "腾讯云 TSE 云原生证书 ID(可选)", + "placeholder": "请输入腾讯云 TSE 云原生证书 ID", + "help": "提示:不填写时,将上传新证书;否则,将替换原证书。", + "tooltip": "这是什么?请参阅 https://console.cloud.tencent.com.cn/rcc/governance" + }, "tencentcloud_vod_endpoint": { "label": "腾讯云接口端点(可选)", "placeholder": "请输入腾讯云云点播接口端点(例如:vod.tencentcloudapi.com)",