From ac4c37524385952af9ed23680818a5314ded274b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 10 Feb 2025 17:59:36 +0800 Subject: [PATCH] feat: add aliyun esa deployer --- go.mod | 2 + go.sum | 4 + internal/deployer/providers.go | 10 ++ internal/domain/provider.go | 1 + .../providers/aliyun-esa/aliyun_esa.go | 137 ++++++++++++++++++ .../providers/aliyun-esa/aliyun_esa_test.go | 80 ++++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../DeployNodeConfigFormAliyunESAConfig.tsx | 78 ++++++++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.common.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 6 + ui/src/i18n/locales/zh/nls.common.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 6 + 13 files changed, 331 insertions(+) create mode 100644 internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormAliyunESAConfig.tsx 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",