diff --git a/go.mod b/go.mod
index 6cd082bf..ac22c2c2 100644
--- a/go.mod
+++ b/go.mod
@@ -56,6 +56,8 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
+ github.com/alibabacloud-go/esa-20240910 v1.0.0 // indirect
+ github.com/alibabacloud-go/esa-20240910/v2 v2.12.0 // indirect
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
diff --git a/go.sum b/go.sum
index 7dde5dc8..edf5ce96 100644
--- a/go.sum
+++ b/go.sum
@@ -141,6 +141,10 @@ github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/ql
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9GQdZO3mcSUTUy8=
github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
+github.com/alibabacloud-go/esa-20240910 v1.0.0 h1:mWWuq/OX9qcNaSvWqRmQKl7zvfZaZEqaQ7re7P4nTg4=
+github.com/alibabacloud-go/esa-20240910 v1.0.0/go.mod h1:uQUr6wAz8x8ClsjeSfBuI63FdRd2LoNShmVzZknKaPk=
+github.com/alibabacloud-go/esa-20240910/v2 v2.12.0 h1:DPyKQSVf+uba5SBEGgBpXRA8NpFuym1cSTKm1LzSxEo=
+github.com/alibabacloud-go/esa-20240910/v2 v2.12.0/go.mod h1:P1w/+i7dE2xSXVHJznEOVImlLtqqrzUJQQk2AsyBJ6o=
github.com/alibabacloud-go/live-20161101 v1.1.1 h1:rUGfA8RHmCMtQ5M3yMSyRde+yRXWqVecmiXBU3XrGJ8=
github.com/alibabacloud-go/live-20161101 v1.1.1/go.mod h1:g84w6qeAodT0/IHdc0tEed2a8PyhQhYl7TAj3jGl4A4=
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 h1:LtyUVlgBEKyzWgQJurzXM6MXCt84sQr9cE5OKqYymko=
diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go
index 344c78e6..9bb3519f 100644
--- a/internal/deployer/providers.go
+++ b/internal/deployer/providers.go
@@ -9,6 +9,7 @@ import (
providerAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn"
providerAliyunCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb"
providerAliyunDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn"
+ providerAliyunESA "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-esa"
providerAliyunLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-live"
providerAliyunNLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb"
providerAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss"
@@ -99,6 +100,15 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
}, logger)
return deployer, logger, err
+ case domain.DeployProviderTypeAliyunESA:
+ deployer, err := providerAliyunESA.NewWithLogger(&providerAliyunESA.AliyunESADeployerConfig{
+ AccessKeyId: access.AccessKeyId,
+ AccessKeySecret: access.AccessKeySecret,
+ Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
+ SiteId: maps.GetValueAsInt64(options.ProviderDeployConfig, "siteId"),
+ }, logger)
+ return deployer, logger, err
+
case domain.DeployProviderTypeAliyunLive:
deployer, err := providerAliyunLive.NewWithLogger(&providerAliyunLive.AliyunLiveDeployerConfig{
AccessKeyId: access.AccessKeyId,
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 894f8007..0b3d7f8d 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -86,6 +86,7 @@ const (
DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn")
DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb")
DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn")
+ DeployProviderTypeAliyunESA = DeployProviderType("aliyun-esa")
DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live")
DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb")
DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss")
diff --git a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go
new file mode 100644
index 00000000..de7a1028
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go
@@ -0,0 +1,137 @@
+package aliyunesa
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+
+ aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
+ aliyunEsa "github.com/alibabacloud-go/esa-20240910/v2/client"
+ "github.com/alibabacloud-go/tea/tea"
+ xerrors "github.com/pkg/errors"
+
+ "github.com/usual2970/certimate/internal/pkg/core/deployer"
+ "github.com/usual2970/certimate/internal/pkg/core/logger"
+ "github.com/usual2970/certimate/internal/pkg/core/uploader"
+ uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
+)
+
+type AliyunESADeployerConfig struct {
+ // 阿里云 AccessKeyId。
+ AccessKeyId string `json:"accessKeyId"`
+ // 阿里云 AccessKeySecret。
+ AccessKeySecret string `json:"accessKeySecret"`
+ // 阿里云地域。
+ Region string `json:"region"`
+ // 阿里云 ESA 站点 ID。
+ SiteId int64 `json:"siteId"`
+}
+
+type AliyunESADeployer struct {
+ config *AliyunESADeployerConfig
+ logger logger.Logger
+ sdkClient *aliyunEsa.Client
+ sslUploader uploader.Uploader
+}
+
+var _ deployer.Deployer = (*AliyunESADeployer)(nil)
+
+func New(config *AliyunESADeployerConfig) (*AliyunESADeployer, error) {
+ return NewWithLogger(config, logger.NewNilLogger())
+}
+
+func NewWithLogger(config *AliyunESADeployerConfig, logger logger.Logger) (*AliyunESADeployer, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ if logger == nil {
+ return nil, errors.New("logger is nil")
+ }
+
+ client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create sdk client")
+ }
+
+ uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create ssl uploader")
+ }
+
+ return &AliyunESADeployer{
+ logger: logger,
+ config: config,
+ sdkClient: client,
+ sslUploader: uploader,
+ }, nil
+}
+
+func (d *AliyunESADeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
+ if d.config.SiteId == 0 {
+ return nil, errors.New("config `siteId` is required")
+ }
+
+ // 上传证书到 CAS
+ upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to upload certificate file")
+ }
+
+ d.logger.Logt("certificate file uploaded", upres)
+
+ // 配置站点证书
+ // REF: https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-setcertificate
+ certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
+ setCertificateReq := &aliyunEsa.SetCertificateRequest{
+ SiteId: tea.Int64(d.config.SiteId),
+ Type: tea.String("cas"),
+ CasId: tea.Int64(certId),
+ }
+ setCertificateResp, err := d.sdkClient.SetCertificate(setCertificateReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'esa.SetCertificate'")
+ }
+
+ d.logger.Logt("已配置站点证书", setCertificateResp)
+
+ return &deployer.DeployResult{}, nil
+}
+
+func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunEsa.Client, error) {
+ config := &aliyunOpen.Config{
+ AccessKeyId: tea.String(accessKeyId),
+ AccessKeySecret: tea.String(accessKeySecret),
+ Endpoint: tea.String(fmt.Sprintf("esa.%s.aliyuncs.com", region)),
+ }
+
+ client, err := aliyunEsa.NewClient(config)
+ if err != nil {
+ return nil, err
+ }
+
+ return client, nil
+}
+
+func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) {
+ casRegion := region
+ if casRegion != "" {
+ // 阿里云 CAS 服务接入点是独立于 ESA 服务的
+ // 国内版固定接入点:华东一杭州
+ // 国际版固定接入点:亚太东南一新加坡
+ if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") {
+ casRegion = "ap-southeast-1"
+ } else {
+ casRegion = "cn-hangzhou"
+ }
+ }
+
+ uploader, err := uploaderp.New(&uploaderp.AliyunCASUploaderConfig{
+ AccessKeyId: accessKeyId,
+ AccessKeySecret: accessKeySecret,
+ Region: casRegion,
+ })
+ return uploader, err
+}
diff --git a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go
new file mode 100644
index 00000000..980dc9a0
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go
@@ -0,0 +1,80 @@
+package aliyunesa_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+
+ provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-esa"
+)
+
+var (
+ fInputCertPath string
+ fInputKeyPath string
+ fAccessKeyId string
+ fAccessKeySecret string
+ fRegion string
+ fSiteId int64
+)
+
+func init() {
+ argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNESA_"
+
+ flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
+ flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
+ flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
+ flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
+ flag.StringVar(&fRegion, argsPrefix+"REGION", "", "")
+ flag.Int64Var(&fSiteId, argsPrefix+"SITEID", "", "")
+}
+
+/*
+Shell command to run this test:
+
+ go test -v ./aliyun_esa_test.go -args \
+ --CERTIMATE_DEPLOYER_ALIYUNESA_INPUTCERTPATH="/path/to/your-input-cert.pem" \
+ --CERTIMATE_DEPLOYER_ALIYUNESA_INPUTKEYPATH="/path/to/your-input-key.pem" \
+ --CERTIMATE_DEPLOYER_ALIYUNESA_ACCESSKEYID="your-access-key-id" \
+ --CERTIMATE_DEPLOYER_ALIYUNESA_ACCESSKEYSECRET="your-access-key-secret" \
+ --CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \
+ --CERTIMATE_DEPLOYER_ALIYUNESA_SITEID="your-esa-site-id"
+*/
+func TestDeploy(t *testing.T) {
+ flag.Parse()
+
+ t.Run("Deploy", func(t *testing.T) {
+ t.Log(strings.Join([]string{
+ "args:",
+ fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
+ fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
+ fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
+ fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
+ fmt.Sprintf("REGION: %v", fRegion),
+ fmt.Sprintf("SITEID: %v", fSiteId),
+ }, "\n"))
+
+ deployer, err := provider.New(&provider.AliyunESADeployerConfig{
+ AccessKeyId: fAccessKeyId,
+ AccessKeySecret: fAccessKeySecret,
+ Region: fRegion,
+ SiteId: fSiteId,
+ })
+ if err != nil {
+ t.Errorf("err: %+v", err)
+ return
+ }
+
+ fInputCertData, _ := os.ReadFile(fInputCertPath)
+ fInputKeyData, _ := os.ReadFile(fInputKeyPath)
+ res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
+ if err != nil {
+ t.Errorf("err: %+v", err)
+ return
+ }
+
+ t.Logf("ok: %v", res)
+ })
+}
diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
index c9b70570..41a91219 100644
--- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
@@ -19,6 +19,7 @@ import DeployNodeConfigFormAliyunALBConfig from "./DeployNodeConfigFormAliyunALB
import DeployNodeConfigFormAliyunCDNConfig from "./DeployNodeConfigFormAliyunCDNConfig";
import DeployNodeConfigFormAliyunCLBConfig from "./DeployNodeConfigFormAliyunCLBConfig";
import DeployNodeConfigFormAliyunDCDNConfig from "./DeployNodeConfigFormAliyunDCDNConfig";
+import DeployNodeConfigFormAliyunESAConfig from "./DeployNodeConfigFormAliyunESAConfig";
import DeployNodeConfigFormAliyunLiveConfig from "./DeployNodeConfigFormAliyunLiveConfig";
import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLBConfig";
import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig";
@@ -129,6 +130,8 @@ const DeployNodeConfigForm = forwardRef;
case DEPLOY_PROVIDERS.ALIYUN_DCDN:
return ;
+ case DEPLOY_PROVIDERS.ALIYUN_ESA:
+ return ;
case DEPLOY_PROVIDERS.ALIYUN_LIVE:
return ;
case DEPLOY_PROVIDERS.ALIYUN_NLB:
diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunESAConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunESAConfig.tsx
new file mode 100644
index 00000000..338c0f97
--- /dev/null
+++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunESAConfig.tsx
@@ -0,0 +1,78 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+type DeployNodeConfigFormAliyunESAConfigFieldValues = Nullish<{
+ region: string;
+ siteId: string;
+}>;
+
+export type DeployNodeConfigFormAliyunESAConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: DeployNodeConfigFormAliyunESAConfigFieldValues;
+ onValuesChange?: (values: DeployNodeConfigFormAliyunESAConfigFieldValues) => void;
+};
+
+const initFormModel = (): DeployNodeConfigFormAliyunESAConfigFieldValues => {
+ return {};
+};
+
+const DeployNodeConfigFormAliyunESAConfig = ({
+ form: formInst,
+ formName,
+ disabled,
+ initialValues,
+ onValuesChange,
+}: DeployNodeConfigFormAliyunESAConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ region: z
+ .string({ message: t("workflow_node.deploy.form.aliyun_esa_region.placeholder") })
+ .nonempty(t("workflow_node.deploy.form.aliyun_esa_region.placeholder"))
+ .trim(),
+ siteId: z
+ .string({ message: t("workflow_node.deploy.form.aliyun_esa_site_id.placeholder") })
+ .regex(/^[1-9]\d*$/, t("workflow_node.deploy.form.aliyun_esa_site_id.placeholder"))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default DeployNodeConfigFormAliyunESAConfig;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index 652f4332..2bc1e9bf 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -179,6 +179,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({
ALIYUN_CDN: `${ACCESS_PROVIDERS.ALIYUN}-cdn`,
ALIYUN_CLB: `${ACCESS_PROVIDERS.ALIYUN}-clb`,
ALIYUN_DCDN: `${ACCESS_PROVIDERS.ALIYUN}-dcdn`,
+ ALIYUN_ESA: `${ACCESS_PROVIDERS.ALIYUN}-esa`,
ALIYUN_LIVE: `${ACCESS_PROVIDERS.ALIYUN}-live`,
ALIYUN_NLB: `${ACCESS_PROVIDERS.ALIYUN}-nlb`,
ALIYUN_OSS: `${ACCESS_PROVIDERS.ALIYUN}-oss`,
@@ -233,6 +234,7 @@ export const deployProvidersMap: Maphttps://dcdn.console.aliyun.com",
+ "workflow_node.deploy.form.aliyun_esa_region.label": "Alibaba Cloud region",
+ "workflow_node.deploy.form.aliyun_esa_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)",
+ "workflow_node.deploy.form.aliyun_esa_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-endpoint",
+ "workflow_node.deploy.form.aliyun_esa_site_id.label": "Alibaba Cloud ESA site ID",
+ "workflow_node.deploy.form.aliyun_esa_site_id.placeholder": "Please enter Alibaba Cloud ESA site ID",
+ "workflow_node.deploy.form.aliyun_esa_site_id.tooltip": "For more information, see https://esa.console.aliyun.com/siteManage/list",
"workflow_node.deploy.form.aliyun_live_region.label": "Alibaba Cloud region",
"workflow_node.deploy.form.aliyun_live_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)",
"workflow_node.deploy.form.aliyun_live_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/live/product-overview/supported-regions",
diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json
index b8b97842..15919d22 100644
--- a/ui/src/i18n/locales/zh/nls.common.json
+++ b/ui/src/i18n/locales/zh/nls.common.json
@@ -41,6 +41,7 @@
"common.provider.aliyun.cdn": "阿里云 - 内容分发网络 CDN",
"common.provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB",
"common.provider.aliyun.dcdn": "阿里云 - 全站加速 DCDN",
+ "common.provider.aliyun.esa": "阿里云 - 边缘安全加速 ESA",
"common.provider.aliyun.dns": "阿里云 - 云解析 DNS",
"common.provider.aliyun.live": "阿里云 - 视频直播 Live",
"common.provider.aliyun.nlb": "阿里云 - 网络型负载均衡 NLB",
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
index d132f935..ea6b43ec 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
@@ -125,6 +125,12 @@
"workflow_node.deploy.form.aliyun_dcdn_domain.label": "阿里云 DCDN 加速域名(支持泛域名)",
"workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "请输入阿里云 DCDN 加速域名",
"workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "这是什么?请参阅 https://dcdn.console.aliyun.com
泛域名表示形式为:*.example.com",
+ "workflow_node.deploy.form.aliyun_esa_region.label": "阿里云地域",
+ "workflow_node.deploy.form.aliyun_esa_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)",
+ "workflow_node.deploy.form.aliyun_esa_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-endpoint",
+ "workflow_node.deploy.form.aliyun_esa_site_id.label": "阿里云 ESA 站点 ID",
+ "workflow_node.deploy.form.aliyun_esa_site_id.placeholder": "请输入阿里云 ESA 站点 ID",
+ "workflow_node.deploy.form.aliyun_esa_site_id.tooltip": "这是什么?请参阅 https://esa.console.aliyun.com/siteManage/list",
"workflow_node.deploy.form.aliyun_live_region.label": "阿里云地域",
"workflow_node.deploy.form.aliyun_live_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)",
"workflow_node.deploy.form.aliyun_live_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/live/product-overview/supported-regions",