diff --git a/internal/certmgmt/deployers/sp_byteplus_tos.go b/internal/certmgmt/deployers/sp_byteplus_tos.go new file mode 100644 index 000000000..e1764ea83 --- /dev/null +++ b/internal/certmgmt/deployers/sp_byteplus_tos.go @@ -0,0 +1,29 @@ +package deployers + +import ( + "fmt" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core" + byteplustos "github.com/certimate-go/certimate/pkg/core/deployer/providers/byteplus-tos" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + Registries.MustRegister(domain.DeploymentProviderTypeBytePlusTOS, func(options *ProviderFactoryOptions) (core.Deployer, error) { + credentials := domain.AccessConfigForBytePlus{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := byteplustos.NewDeployer(&byteplustos.DeployerConfig{ + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + ProjectName: credentials.ProjectName, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), + Bucket: xmaps.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + }) + return provider, err + }) +} diff --git a/internal/domain/provider.go b/internal/domain/provider.go index fc814988d..d5420fd6b 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -340,6 +340,7 @@ const ( DeploymentProviderTypeBunnyCDN = DeploymentProviderType(AccessProviderTypeBunny + "-cdn") DeploymentProviderTypeBytePlusCDN = DeploymentProviderType(AccessProviderTypeBytePlus + "-cdn") DeploymentProviderTypeBytePlusCertCenter = DeploymentProviderType(AccessProviderTypeBytePlus + "-certcenter") + DeploymentProviderTypeBytePlusTOS = DeploymentProviderType(AccessProviderTypeBytePlus + "-tos") DeploymentProviderTypeCacheFly = DeploymentProviderType(AccessProviderTypeCacheFly) DeploymentProviderTypeCdnfly = DeploymentProviderType(AccessProviderTypeCdnfly) DeploymentProviderTypeCPanel = DeploymentProviderType(AccessProviderTypeCPanel) diff --git a/pkg/core/deployer/providers/byteplus-tos/byteplus_tos.go b/pkg/core/deployer/providers/byteplus-tos/byteplus_tos.go new file mode 100644 index 000000000..4333616ed --- /dev/null +++ b/pkg/core/deployer/providers/byteplus-tos/byteplus_tos.go @@ -0,0 +1,125 @@ +package byteplustos + +import ( + "context" + "fmt" + "log/slog" + + "github.com/volcengine/ve-tos-golang-sdk/v2/tos" + + "github.com/certimate-go/certimate/pkg/core/certmgr" + certmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/byteplus-certcenter" + "github.com/certimate-go/certimate/pkg/core/deployer" +) + +type DeployerConfig struct { + // BytePlus AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // BytePlus SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // BytePlus 项目名称。 + ProjectName string `json:"projectName,omitempty"` + // BytePlus 地域。 + Region string `json:"region"` + // 存储桶名。 + Bucket string `json:"bucket"` + // 自定义域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type Deployer struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *tos.ClientV2 + sdkCertmgr certmgr.Provider +} + +var _ deployer.Provider = (*Deployer)(nil) + +func NewDeployer(config *DeployerConfig) (*Deployer, error) { + if config == nil { + return nil, fmt.Errorf("the configuration of the deployer provider is nil") + } + + client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region) + if err != nil { + return nil, fmt.Errorf("could not create client: %w", err) + } + + pcertmgr, err := certmgrimpl.NewCertmgr(&certmgrimpl.CertmgrConfig{ + AccessKeyId: config.AccessKeyId, + SecretAccessKey: config.SecretAccessKey, + ProjectName: config.ProjectName, + Region: config.Region, + }) + if err != nil { + return nil, fmt.Errorf("could not create certmgr: %w", err) + } + + return &Deployer{ + config: config, + logger: slog.Default(), + sdkClient: client, + 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) (*deployer.DeployResult, error) { + if d.config.Bucket == "" { + return nil, fmt.Errorf("config `bucket` is required") + } + if d.config.Domain == "" { + return nil, fmt.Errorf("config `domain` is required") + } + + // 上传证书 + 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)) + } + + // 设置自定义域名 + // REF: https://docs.byteplus.com/en/docs/tos/Putbucketcustomdomain + // REF: https://docs.byteplus.com/en/docs/tos/Manage-custom-domain-names-go-sdk + putBucketCustomDomainReq := &tos.PutBucketCustomDomainInput{ + Bucket: d.config.Bucket, + Rule: tos.CustomDomainRule{ + Domain: d.config.Domain, + CertID: upres.CertId, + }, + } + putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomain(ctx, putBucketCustomDomainReq) + d.logger.Debug("sdk request 'tos.PutBucketCustomDomain'", slog.Any("request", putBucketCustomDomainReq), slog.Any("response", putBucketCustomDomainResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'tos.PutBucketCustomDomain': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSDKClient(accessKeyId, secretAccessKey, region string) (*tos.ClientV2, error) { + endpoint := fmt.Sprintf("tos-%s.bytepluses.com", region) + + client, err := tos.NewClientV2( + endpoint, + tos.WithRegion(region), + tos.WithCredentials(tos.NewStaticCredentials(accessKeyId, secretAccessKey)), + ) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/pkg/core/deployer/providers/byteplus-tos/byteplus_tos_test.go b/pkg/core/deployer/providers/byteplus-tos/byteplus_tos_test.go new file mode 100644 index 000000000..7319be6ae --- /dev/null +++ b/pkg/core/deployer/providers/byteplus-tos/byteplus_tos_test.go @@ -0,0 +1,61 @@ +package byteplustos_test + +import ( + "testing" + + "github.com/certimate-go/certimate/pkg/core/deployer/internal/tester" + impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/byteplus-tos" +) + +var ( + fp = tester.Args("BYTEPLUSTOS_") + fTestCertPath string + fTestKeyPath string + fAccessKeyId string + fSecretAccessKey string + fRegion string + fBucket string + fDomain string +) + +func init() { + fp.DefineString(&fTestCertPath, "TESTCERTPATH") + fp.DefineString(&fTestKeyPath, "TESTKEYPATH") + fp.DefineString(&fAccessKeyId, "ACCESSKEYID") + fp.DefineString(&fSecretAccessKey, "SECRETACCESSKEY") + fp.DefineString(&fRegion, "REGION") + fp.DefineString(&fBucket, "BUCKET") + fp.DefineString(&fDomain, "DOMAIN") +} + +/* +Shell command to run this test: + + go test -v ./byteplus_tos_test.go -args \ + --BYTEPLUSTOS_TESTCERTPATH="/path/to/your-test-cert.pem" \ + --BYTEPLUSTOS_TESTKEYPATH="/path/to/your-test-key.pem" \ + --BYTEPLUSTOS_ACCESSKEYID="your-access-key-id" \ + --BYTEPLUSTOS_SECRETACCESSKEY="your-secret-access-key" \ + --BYTEPLUSTOS_REGION="cn-beijing" \ + --BYTEPLUSTOS_BUCKET="your-tos-bucket" \ + --BYTEPLUSTOS_DOMAIN="example.com" +*/ +func TestProvider(t *testing.T) { + fp.Parse() + + t.Run("Deploy", func(t *testing.T) { + provider, err := impl.NewDeployer(&impl.DeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + Region: fRegion, + Bucket: fBucket, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + tester.TestDeploy(t, provider, tester.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath}) + }) +} diff --git a/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go b/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go index ac37ebfca..598bff2a9 100644 --- a/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go +++ b/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go @@ -196,7 +196,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep return nil, fmt.Errorf("failed to parse private key: %w", err) } - privkeyAlg, _, _ := xcertkey.DetectPrivateKeyAlgorithm(privkey) + privkeyAlg, _, _ := xcertkey.GetPrivateKeyAlgorithm(privkey) privkeyAlgStr := "" switch privkeyAlg { case x509.RSA: diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 976b66a2a..9e59b8a83 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -623,6 +623,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ BUNNY_CDN: `${ACCESS_PROVIDERS.BUNNY}-cdn`, BYTEPLUS_CDN: `${ACCESS_PROVIDERS.BYTEPLUS}-cdn`, BYTEPLUS_CERTCENTER: `${ACCESS_PROVIDERS.BYTEPLUS}-certcenter`, + BYTEPLUS_TOS: `${ACCESS_PROVIDERS.BYTEPLUS}-tos`, CACHEFLY: `${ACCESS_PROVIDERS.CACHEFLY}`, CDNFLY: `${ACCESS_PROVIDERS.CDNFLY}`, CPANEL: `${ACCESS_PROVIDERS.CPANEL}`, @@ -825,6 +826,7 @@ export const deploymentProvidersMap: Maphttps://www.volcengine.com/docs/6349/107356" + }, + "byteplus_tos_bucket": { + "label": "BytePlus TOS bucket", + "placeholder": "Please enter BytePlus TOS bucket name" + }, + "byteplus_tos_domain": { + "label": "BytePlus TOS custom domain", + "placeholder": "Please enter BytePlus TOS bucket custom domain name" + }, "cdnfly_deploy_target": { "option": { "website": { diff --git a/ui/src/i18n/resources/zh/nls.provider.json b/ui/src/i18n/resources/zh/nls.provider.json index 6e6bbede1..a7de94e19 100644 --- a/ui/src/i18n/resources/zh/nls.provider.json +++ b/ui/src/i18n/resources/zh/nls.provider.json @@ -92,6 +92,7 @@ "byteplus_cdn": "BytePlus - 内容分发网络 CDN", "byteplus_certcenter_upload": "BytePlus - 上传到证书中心", "byteplus_dns": "BytePlus - DNS", + "byteplus_tos": "BytePlus - 对象存储 TOS", "cachefly": "CacheFly", "cdnfly": "Cdnfly", "cloudflare": "Cloudflare", diff --git a/ui/src/i18n/resources/zh/nls.workflow.nodes.json b/ui/src/i18n/resources/zh/nls.workflow.nodes.json index e290fc7c6..508755734 100644 --- a/ui/src/i18n/resources/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/resources/zh/nls.workflow.nodes.json @@ -1213,9 +1213,22 @@ "placeholder": "请输入 BytePlus CDN 域名" }, "byteplus_certcenter_region": { - "label": "BytePlus 证书中心服务地域", + "label": "BytePlus 服务地域", "placeholder": "请输入 BytePlus 证书中心服务地域(例如:ap-singapore-1)" }, + "byteplus_tos_region": { + "label": "BytePlus 服务地域", + "placeholder": "请输入 BytePlus TOS 服务地域(例如:ap-singapore-1)", + "tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6349/107356" + }, + "byteplus_tos_bucket": { + "label": "BytePlus TOS 存储桶名", + "placeholder": "请输入 BytePlus TOS 存储桶名" + }, + "byteplus_tos_domain": { + "label": "BytePlus TOS 自定义域名", + "placeholder": "请输入 BytePlus TOS 自定义域名" + }, "cdnfly_deploy_target": { "option": { "website": {