From adb43dfee180a42c70435d3fae742a9eebe64265 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 14:24:59 +0800 Subject: [PATCH 01/11] feat: add qiniu pili deployer --- README.md | 2 +- README_EN.md | 2 +- go.mod | 6 ++ go.sum | 10 ++ internal/deployer/providers.go | 30 ++++-- internal/domain/provider.go | 1 + .../providers/qiniu-pili/qiniu_pili.go | 91 +++++++++++++++++++ .../providers/qiniu-pili/qiniu_pili_test.go | 79 ++++++++++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../DeployNodeConfigFormQiniuPiliConfig.tsx | 79 ++++++++++++++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.common.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 12 ++- ui/src/i18n/locales/zh/nls.common.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 8 +- 15 files changed, 314 insertions(+), 13 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go create mode 100644 internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormQiniuPiliConfig.tsx diff --git a/README.md b/README.md index 745cec19..ed568a15 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ make local.run | [百度智能云](https://cloud.baidu.com/) | 可部署到百度智能云 CDN 等服务 | | [华为云](https://www.huaweicloud.com/) | 可部署到华为云 CDN、ELB 等服务 | | [火山引擎](https://www.volcengine.com/) | 可部署到火山引擎 TOS、CDN、DCDN、CLB、Live 等服务 | -| [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN | +| [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN、直播云等服务 | | [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN | | [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 | | [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 | diff --git a/README_EN.md b/README_EN.md index ece9591c..a9f2cf90 100644 --- a/README_EN.md +++ b/README_EN.md @@ -122,7 +122,7 @@ The following hosting providers are supported: | [Baidu AI Cloud](https://intl.cloud.baidu.com/) | Supports deployment to Baidu AI CLoud CDN | | [Huawei Cloud](https://www.huaweicloud.com/) | Supports deployment to Huawei Cloud CDN, ELB | | [Volcengine](https://www.volcengine.com/) | Supports deployment to Volcengine TOS, CDN, DCDN, CLB, Live | -| [Qiniu Cloud](https://www.qiniu.com/) | Supports deployment to Qiniu Cloud CDN | +| [Qiniu Cloud](https://www.qiniu.com/) | Supports deployment to Qiniu Cloud CDN, Pili | | [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN | | [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN | | [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN | diff --git a/go.mod b/go.mod index 1567878b..e6e4535a 100644 --- a/go.mod +++ b/go.mod @@ -66,6 +66,9 @@ require ( github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect @@ -75,10 +78,13 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/qiniu/dyn v1.3.0 // indirect + github.com/qiniu/x v1.10.5 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect diff --git a/go.sum b/go.sum index bbb65a6a..efb9e7cf 100644 --- a/go.sum +++ b/go.sum @@ -390,9 +390,15 @@ github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRi github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= +github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-resty/resty/v2 v2.16.4 h1:81IjtszQKwbz7dot4LLYGwhJNUsNwECD2O7nru5q60E= github.com/go-resty/resty/v2 v2.16.4/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -615,6 +621,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -745,9 +753,11 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/qiniu/dyn v1.3.0 h1:s+xPTeV0H8yikgM4ZMBc7Rrefam8UNI3asBlkaOQg5o= github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= github.com/qiniu/go-sdk/v7 v7.25.2 h1:URwgZpxySdiwu2yQpHk93X4LXWHyFRp1x3Vmlk/YWvo= github.com/qiniu/go-sdk/v7 v7.25.2/go.mod h1:dmKtJ2ahhPWFVi9o1D5GemmWoh/ctuB9peqTowyTO8o= +github.com/qiniu/x v1.10.5 h1:7V/CYWEmo9axJULvrJN6sMYh2FdY+esN5h8jwDkA4b0= github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 6fd3457c..4b525b47 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -21,6 +21,7 @@ import ( providerK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret" providerLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" providerQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn" + providerQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili" providerSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" providerTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn" providerTencentCloudCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-clb" @@ -260,19 +261,34 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, return deployer, logger, err } - case domain.DeployProviderTypeQiniuCDN: + case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuPili: { access := domain.AccessConfigForQiniu{} if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } - deployer, err := providerQiniuCDN.NewWithLogger(&providerQiniuCDN.QiniuCDNDeployerConfig{ - AccessKey: access.AccessKey, - SecretKey: access.SecretKey, - Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), - }, logger) - return deployer, logger, err + switch options.Provider { + case domain.DeployProviderTypeQiniuCDN: + deployer, err := providerQiniuCDN.NewWithLogger(&providerQiniuCDN.QiniuCDNDeployerConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), + }, logger) + return deployer, logger, err + + case domain.DeployProviderTypeQiniuPili: + deployer, err := providerQiniuPili.NewWithLogger(&providerQiniuPili.QiniuPiliDeployerConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + Hub: maps.GetValueAsString(options.ProviderDeployConfig, "hub"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), + }, logger) + return deployer, logger, err + + default: + break + } } case domain.DeployProviderTypeSSH: diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 34666531..85b1dd60 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -90,6 +90,7 @@ const ( DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret") DeployProviderTypeLocal = DeployProviderType("local") DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn") + DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili") DeployProviderTypeSSH = DeployProviderType("ssh") DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn") DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb") diff --git a/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go new file mode 100644 index 00000000..9ae267ba --- /dev/null +++ b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go @@ -0,0 +1,91 @@ +package qiniupili + +import ( + "context" + "errors" + + xerrors "github.com/pkg/errors" + "github.com/qiniu/go-sdk/v7/pili" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + providerQiniu "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert" +) + +type QiniuPiliDeployerConfig struct { + // 七牛云 AccessKey。 + AccessKey string `json:"accessKey"` + // 七牛云 SecretKey。 + SecretKey string `json:"secretKey"` + // 直播空间名。 + Hub string `json:"hub"` + // 直播流域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type QiniuPiliDeployer struct { + config *QiniuPiliDeployerConfig + logger logger.Logger + sdkClient *pili.Manager + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*QiniuPiliDeployer)(nil) + +func New(config *QiniuPiliDeployerConfig) (*QiniuPiliDeployer, error) { + return NewWithLogger(config, logger.NewNilLogger()) +} + +func NewWithLogger(config *QiniuPiliDeployerConfig, logger logger.Logger) (*QiniuPiliDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + manager := pili.NewManager(pili.ManagerConfig{AccessKey: config.AccessKey, SecretKey: config.SecretKey}) + + uploader, err := providerQiniu.New(&providerQiniu.QiniuSSLCertUploaderConfig{ + AccessKey: config.AccessKey, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &QiniuPiliDeployer{ + logger: logger, + config: config, + sdkClient: manager, + sslUploader: uploader, + }, nil +} + +func (d *QiniuPiliDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CDN + 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://developer.qiniu.com/pili/9910/pili-service-sdk#66 + setDomainCertReq := pili.SetDomainCertRequest{ + Hub: d.config.Hub, + Domain: d.config.Domain, + CertName: upres.CertName, + } + err = d.sdkClient.SetDomainCert(context.TODO(), setDomainCertReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'pili.SetDomainCert'") + } + + d.logger.Logt("已修改域名证书配置") + + return &deployer.DeployResult{}, nil +} diff --git a/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go new file mode 100644 index 00000000..86449ce2 --- /dev/null +++ b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go @@ -0,0 +1,79 @@ +package qiniupili_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKey string + fSecretKey string + fHub string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_QINIUPILI_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fHub, argsPrefix+"HUB", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./qiniu_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_QINIUPILI_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_QINIUPILI_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_QINIUPILI_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_QINIUPILI_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_QINIUPILI_HUB="your-hub-name" \ + --CERTIMATE_DEPLOYER_QINIUPILI_DOMAIN="example.com" \ +*/ +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("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("HUB: %v", fHub), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.New(&provider.QiniuPiliDeployerConfig{ + AccessKey: fAccessKey, + SecretKey: fSecretKey, + Domain: fDomain, + }) + 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 5e11fb2a..bac8b948 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -31,6 +31,7 @@ import DeployNodeConfigFormHuaweiCloudELBConfig from "./DeployNodeConfigFormHuaw import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKubernetesSecretConfig"; import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig"; import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig"; +import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig"; import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx"; import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx"; import DeployNodeConfigFormTencentCloudCLBConfig from "./DeployNodeConfigFormTencentCloudCLBConfig.tsx"; @@ -150,6 +151,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.QINIU_CDN: return ; + case DEPLOY_PROVIDERS.QINIU_PILI: + return ; case DEPLOY_PROVIDERS.SSH: return ; case DEPLOY_PROVIDERS.TENCENTCLOUD_CDN: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormQiniuPiliConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormQiniuPiliConfig.tsx new file mode 100644 index 00000000..dd6f1570 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormQiniuPiliConfig.tsx @@ -0,0 +1,79 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormQiniuPiliConfigFieldValues = Nullish<{ + hub: string; + domain: string; +}>; + +export type DeployNodeConfigFormQiniuPiliConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormQiniuPiliConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormQiniuPiliConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormQiniuPiliConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormQiniuPiliConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormQiniuPiliConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + hub: z + .string({ message: t("workflow_node.deploy.form.qiniu_pili_hub.placeholder") }) + .nonempty(t("workflow_node.deploy.form.qiniu_pili_hub.placeholder")) + .trim(), + domain: z + .string({ message: t("workflow_node.deploy.form.qiniu_pili_domain.placeholder") }) + .refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default DeployNodeConfigFormQiniuPiliConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 574c022a..89d30772 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -175,6 +175,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({ KUBERNETES_SECRET: `${ACCESS_PROVIDERS.KUBERNETES}-secret`, LOCAL: `${ACCESS_PROVIDERS.LOCAL}`, QINIU_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`, + QINIU_PILI: `${ACCESS_PROVIDERS.QINIU}-pili`, SSH: `${ACCESS_PROVIDERS.SSH}`, TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`, TENCENTCLOUD_CLB: `${ACCESS_PROVIDERS.TENCENTCLOUD}-clb`, @@ -233,6 +234,7 @@ export const deployProvidersMap: Maphttps://portal.qiniu.com/", + "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "For more information, see https://portal.qiniu.com/cdn", + "workflow_node.deploy.form.qiniu_pili_hub.label": "Qiniu Pili hub", + "workflow_node.deploy.form.qiniu_pili_hub.placeholder": "Please enter Qiniu Pili hub name", + "workflow_node.deploy.form.qiniu_pili_hub.tooltip": "For more information, see https://portal.qiniu.com/hub", + "workflow_node.deploy.form.qiniu_pili_domain.label": "Qiniu Pili streaming domain", + "workflow_node.deploy.form.qiniu_pili_domain.placeholder": "Please enter Qiniu Pili streaming domain name", + "workflow_node.deploy.form.qiniu_pili_domain.tooltip": "For more information, see https://portal.qiniu.com/hub", "workflow_node.deploy.form.ssh_format.label": "File format", "workflow_node.deploy.form.ssh_format.placeholder": "Please select file format", "workflow_node.deploy.form.ssh_format.option.pem.label": "PEM (*.pem, *.crt, *.key)", @@ -345,8 +351,8 @@ "workflow_node.deploy.form.volcengine_dcdn_domain.label": "VolcEngine DCDN domain", "workflow_node.deploy.form.volcengine_dcdn_domain.placeholder": "Please enter VolcEngine DCDN domain name", "workflow_node.deploy.form.volcengine_dcdn_domain.tooltip": "For more information, see https://console.volcengine.com/dcdn/dashboard", - "workflow_node.deploy.form.volcengine_live_domain.label": "VolcEngine live streaming domain", - "workflow_node.deploy.form.volcengine_live_domain.placeholder": "Please enter VolcEngine live streaming domain name", + "workflow_node.deploy.form.volcengine_live_domain.label": "VolcEngine Live streaming domain", + "workflow_node.deploy.form.volcengine_live_domain.placeholder": "Please enter VolcEngine Live streaming domain name", "workflow_node.deploy.form.volcengine_live_domain.tooltip": "For more information, see https://console.volcengine.com/live", "workflow_node.deploy.form.volcengine_tos_region.label": "VolcEngine region", "workflow_node.deploy.form.volcengine_tos_region.placeholder": "Please enter VolcEngine region (e.g. cn-beijing)", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 87fce337..feca7718 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -72,6 +72,7 @@ "common.provider.powerdns": "PowerDNS", "common.provider.qiniu": "七牛云", "common.provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN", + "common.provider.qiniu.pili": "七牛云 - 视频直播 Pili", "common.provider.ssh": "SSH 部署", "common.provider.tencentcloud": "腾讯云", "common.provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 4eddbd92..144c0b58 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -241,7 +241,13 @@ "workflow_node.deploy.form.local_preset_scripts.option.binding_netsh.label": "PowerShell - 导入并绑定到 netsh(需管理员权限)", "workflow_node.deploy.form.qiniu_cdn_domain.label": "七牛云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.qiniu_cdn_domain.placeholder": "请输入七牛云 CDN 加速域名", - "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/

泛域名表示形式为:*.example.com", + "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "这是什么?请参阅 https://portal.qiniu.com/cdn

泛域名表示形式为:*.example.com", + "workflow_node.deploy.form.qiniu_pili_hub.label": "七牛云视频直播空间名", + "workflow_node.deploy.form.qiniu_pili_hub.placeholder": "请输入七牛云视频直播空间名", + "workflow_node.deploy.form.qiniu_pili_hub.tooltip": "这是什么?请参阅 https://portal.qiniu.com/hub", + "workflow_node.deploy.form.qiniu_pili_domain.label": "七牛云视频直播流域名", + "workflow_node.deploy.form.qiniu_pili_domain.placeholder": "请输入七牛云视频直播流域名", + "workflow_node.deploy.form.qiniu_pili_domain.tooltip": "这是什么?请参阅 https://portal.qiniu.com/hub", "workflow_node.deploy.form.ssh_format.label": "文件格式", "workflow_node.deploy.form.ssh_format.placeholder": "请选择文件格式", "workflow_node.deploy.form.ssh_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key)", From 6c3c29dd1117184ba112f2bb2db2c4d7464e7732 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 15:41:22 +0800 Subject: [PATCH 02/11] feat: add westcn applicant --- README.md | 1 + README_EN.md | 1 + go.mod | 1 + go.sum | 2 + internal/applicant/providers.go | 17 +++++ internal/domain/access.go | 5 ++ internal/domain/provider.go | 2 + .../lego-providers/westcn/westcn.go | 39 ++++++++++ ui/public/imgs/providers/westcn.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormWestcnConfig.tsx | 76 +++++++++++++++++++ ui/src/domain/access.ts | 6 ++ ui/src/domain/provider.ts | 6 +- ui/src/i18n/locales/en/nls.access.json | 38 ++++++---- ui/src/i18n/locales/en/nls.common.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 38 ++++++---- ui/src/i18n/locales/zh/nls.common.json | 1 + 17 files changed, 205 insertions(+), 33 deletions(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn/westcn.go create mode 100644 ui/public/imgs/providers/westcn.svg create mode 100644 ui/src/components/access/AccessFormWestcnConfig.tsx diff --git a/README.md b/README.md index ed568a15..762af031 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ make local.run | [Name.com](https://www.name.com/) | | | [NameSilo](https://www.namesilo.com/) | | | [IBM NS1 Connect](https://www.ibm.com/cn-zh/products/ns1-connect/) | | +| [西部数码](https://www.west.cn/) | | | [PowerDNS](https://www.powerdns.com/) | | | ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 | diff --git a/README_EN.md b/README_EN.md index a9f2cf90..fbb97125 100644 --- a/README_EN.md +++ b/README_EN.md @@ -98,6 +98,7 @@ The following DNS providers are supported: | [Name.com](https://www.name.com/) | | | [NameSilo](https://www.namesilo.com/) | | | [IBM NS1 Connect](https://www.ibm.com/products/ns1-connect/) | | +| [West.cn](https://www.west.cn/) | | | [PowerDNS](https://www.powerdns.com/) | | | ACME Proxy HTTP Request | Supports managing DNS by HTTP request | diff --git a/go.mod b/go.mod index e6e4535a..04da51d9 100644 --- a/go.mod +++ b/go.mod @@ -82,6 +82,7 @@ require ( github.com/mailru/easyjson v0.9.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect + github.com/nrdcg/mailinabox v0.2.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/qiniu/dyn v1.3.0 // indirect github.com/qiniu/x v1.10.5 // indirect diff --git a/go.sum b/go.sum index efb9e7cf..831e78c0 100644 --- a/go.sum +++ b/go.sum @@ -683,6 +683,8 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs= github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM= +github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= +github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc= github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 248af1b4..41541f01 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -19,6 +19,7 @@ import ( providerPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns" providerTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud" providerVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine" + providerWestcn "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn" "github.com/usual2970/certimate/internal/pkg/utils/maps" ) @@ -238,6 +239,22 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { }) return applicant, err } + + case domain.ApplyDNSProviderTypeWestcn: + { + access := domain.AccessConfigForWestcn{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) + } + + applicant, err := providerWestcn.NewChallengeProvider(&providerWestcn.WestcnApplicantConfig{ + Username: access.Username, + ApiPassword: access.ApiPassword, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } } return nil, fmt.Errorf("unsupported applicant provider: %s", string(options.Provider)) diff --git a/internal/domain/access.go b/internal/domain/access.go index dda7d2b4..d045853a 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -140,3 +140,8 @@ type AccessConfigForVolcEngine struct { type AccessConfigForWebhook struct { Url string `json:"url"` } + +type AccessConfigForWestcn struct { + Username string `json:"username"` + ApiPassword string `json:"password"` +} diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 85b1dd60..32835e6c 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -32,6 +32,7 @@ const ( AccessProviderTypeUCloud = AccessProviderType("ucloud") AccessProviderTypeVolcEngine = AccessProviderType("volcengine") AccessProviderTypeWebhook = AccessProviderType("webhook") + AccessProviderTypeWestcn = AccessProviderType("westcn") ) type ApplyDNSProviderType string @@ -62,6 +63,7 @@ const ( ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns") ApplyDNSProviderTypeVolcEngine = ApplyDNSProviderType("volcengine") // 兼容旧值,等同于 [ApplyDNSProviderTypeVolcEngineDNS] ApplyDNSProviderTypeVolcEngineDNS = ApplyDNSProviderType("volcengine-dns") + ApplyDNSProviderTypeWestcn = ApplyDNSProviderType("westcn") ) type DeployProviderType string diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn/westcn.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn/westcn.go new file mode 100644 index 00000000..f20b5d21 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn/westcn.go @@ -0,0 +1,39 @@ +package westcn + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/providers/dns/westcn" +) + +type WestcnApplicantConfig struct { + Username string `json:"username"` + ApiPassword string `json:"apiPassword"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *WestcnApplicantConfig) (challenge.Provider, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + providerConfig := westcn.NewDefaultConfig() + providerConfig.Username = config.Username + providerConfig.Password = config.ApiPassword + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := westcn.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/westcn.svg b/ui/public/imgs/providers/westcn.svg new file mode 100644 index 00000000..9ea5a83c --- /dev/null +++ b/ui/public/imgs/providers/westcn.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index afe49ccd..296eec43 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -32,6 +32,7 @@ import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig"; import AccessFormUCloudConfig from "./AccessFormUCloudConfig"; import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig"; import AccessFormWebhookConfig from "./AccessFormWebhookConfig"; +import AccessFormWestcnConfig from "./AccessFormWestcnConfig"; type AccessFormFieldValues = Partial>; type AccessFormPresets = "add" | "edit"; @@ -131,6 +132,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.WEBHOOK: return ; + case ACCESS_PROVIDERS.WESTCN: + return ; } }, [disabled, initialValues?.config, fieldProvider, nestedFormInst, nestedFormName]); diff --git a/ui/src/components/access/AccessFormWestcnConfig.tsx b/ui/src/components/access/AccessFormWestcnConfig.tsx new file mode 100644 index 00000000..0b0257ed --- /dev/null +++ b/ui/src/components/access/AccessFormWestcnConfig.tsx @@ -0,0 +1,76 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForWestcn } from "@/domain/access"; + +type AccessFormWestcnConfigFieldValues = Nullish; + +export type AccessFormWestcnConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormWestcnConfigFieldValues; + onValuesChange?: (values: AccessFormWestcnConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormWestcnConfigFieldValues => { + return { + username: "", + apiPassword: "", + }; +}; + +const AccessFormWestcnConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormWestcnConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + username: z + .string() + .trim() + .min(1, t("access.form.westcn_username.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })), + apiPassword: z + .string() + .min(1, t("access.form.westcn_api_password.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormWestcnConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 0c0d6986..19167ddc 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -30,6 +30,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForUCloud | AccessConfigForVolcEngine | AccessConfigForWebhook + | AccessConfigForWestcn ); usage: AccessUsageType; } @@ -150,4 +151,9 @@ export type AccessConfigForVolcEngine = { export type AccessConfigForWebhook = { url: string; }; + +export type AccessConfigForWestcn = { + username: string; + apiPassword: string; +}; // #endregion diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 89d30772..cb8acfb1 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -27,6 +27,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ UCLOUD: "ucloud", VOLCENGINE: "volcengine", WEBHOOK: "webhook", + WESTCN: "westcn", } as const); export type AccessProviderType = (typeof ACCESS_PROVIDERS)[keyof typeof ACCESS_PROVIDERS]; @@ -69,10 +70,11 @@ export const accessProvidersMap: Map [ @@ -111,6 +113,7 @@ export const APPLY_DNS_PROVIDERS = Object.freeze({ TENCENTCLOUD_DNS: `${ACCESS_PROVIDERS.TENCENTCLOUD}-dns`, VOLCENGINE: `${ACCESS_PROVIDERS.VOLCENGINE}`, // 兼容旧值,等同于 `VOLCENGINE_DNS` VOLCENGINE_DNS: `${ACCESS_PROVIDERS.VOLCENGINE}-dns`, + WESTCN: `${ACCESS_PROVIDERS.WESTCN}`, } as const); export type ApplyDNSProviderType = (typeof APPLY_DNS_PROVIDERS)[keyof typeof APPLY_DNS_PROVIDERS]; @@ -139,6 +142,7 @@ export const applyDNSProvidersMap: Map [ diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 47f59120..36e0a904 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -124,6 +124,22 @@ "access.form.qiniu_secret_key.label": "Qiniu SecretKey", "access.form.qiniu_secret_key.placeholder": "Please enter Qiniu SecretKey", "access.form.qiniu_secret_key.tooltip": "For more information, see https://portal.qiniu.com/", + "access.form.ssh_host.label": "Server host", + "access.form.ssh_host.placeholder": "Please enter server host", + "access.form.ssh_port.label": "Server port", + "access.form.ssh_port.placeholder": "Please enter server port", + "access.form.ssh_username.label": "Username", + "access.form.ssh_username.placeholder": "Please enter username", + "access.form.ssh_password.label": "Password", + "access.form.ssh_password.placeholder": "Please enter password", + "access.form.ssh_password.tooltip": "Required when using password to connect to SSH.", + "access.form.ssh_key.label": "SSH key", + "access.form.ssh_key.placeholder": "Please enter SSH key", + "access.form.ssh_key.upload": "Choose file ...", + "access.form.ssh_key.tooltip": "Required when using key to connect to SSH.", + "access.form.ssh_key_passphrase.label": "SSH key passphrase", + "access.form.ssh_key_passphrase.placeholder": "Please enter SSH key passphrase", + "access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH.", "access.form.tencentcloud_secret_id.label": "Tencent Cloud SecretId", "access.form.tencentcloud_secret_id.placeholder": "Please enter Tencent Cloud SecretId", "access.form.tencentcloud_secret_id.tooltip": "For more information, see https://cloud.tencent.com/document/product/598/40488?lang=en", @@ -147,20 +163,10 @@ "access.form.volcengine_secret_access_key.tooltip": "For more information, see https://www.volcengine.com/docs/6291/216571", "access.form.webhook_url.label": "Webhook URL", "access.form.webhook_url.placeholder": "Please enter Webhook URL", - "access.form.ssh_host.label": "Server host", - "access.form.ssh_host.placeholder": "Please enter server host", - "access.form.ssh_port.label": "Server port", - "access.form.ssh_port.placeholder": "Please enter server port", - "access.form.ssh_username.label": "Username", - "access.form.ssh_username.placeholder": "Please enter username", - "access.form.ssh_password.label": "Password", - "access.form.ssh_password.placeholder": "Please enter password", - "access.form.ssh_password.tooltip": "Required when using password to connect to SSH.", - "access.form.ssh_key.label": "SSH key", - "access.form.ssh_key.placeholder": "Please enter SSH key", - "access.form.ssh_key.upload": "Choose file ...", - "access.form.ssh_key.tooltip": "Required when using key to connect to SSH.", - "access.form.ssh_key_passphrase.label": "SSH key passphrase", - "access.form.ssh_key_passphrase.placeholder": "Please enter SSH key passphrase", - "access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH." + "access.form.westcn_username.label": "West.cn username", + "access.form.westcn_username.placeholder": "Please enter West.cn username", + "access.form.westcn_username.tooltip": "For more information, see https://www.west.cn/CustomerCenter/doc/apiv2.html", + "access.form.westcn_api_password.label": "West.cn API password", + "access.form.westcn_api_password.placeholder": "Please enter West.cn API password", + "access.form.westcn_api_password.tooltip": "For more information, see https://www.west.cn/CustomerCenter/doc/apiv2.html" } diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 17764565..3bed9469 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -93,6 +93,7 @@ "common.provider.volcengine.live": "Volcengine - Live", "common.provider.volcengine.tos": "Volcengine - Tinder Object Storage (TOS)", "common.provider.webhook": "Webhook", + "common.provider.westcn": "West.cn", "common.notifier.bark": "Bark", "common.notifier.dingtalk": "DingTalk", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 35a10eda..d3baaf67 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -124,6 +124,22 @@ "access.form.qiniu_secret_key.label": "七牛云 SecretKey", "access.form.qiniu_secret_key.placeholder": "请输入七牛云 SecretKey", "access.form.qiniu_secret_key.tooltip": "这是什么?请参阅 https://portal.qiniu.com/", + "access.form.ssh_host.label": "服务器地址", + "access.form.ssh_host.placeholder": "请输入服务器地址", + "access.form.ssh_port.label": "服务器端口", + "access.form.ssh_port.placeholder": "请输入服务器端口", + "access.form.ssh_username.label": "用户名", + "access.form.ssh_username.placeholder": "请输入用户名", + "access.form.ssh_password.label": "密码", + "access.form.ssh_password.placeholder": "请输入密码", + "access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。
该字段与密钥文件字段二选一。", + "access.form.ssh_key.label": "SSH 密钥", + "access.form.ssh_key.placeholder": "请输入 SSH 密钥文件", + "access.form.ssh_key.upload": "选择文件", + "access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。
该字段与密码字段二选一。", + "access.form.ssh_key_passphrase.label": "SSH 密钥口令", + "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令", + "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。", "access.form.tencentcloud_secret_id.label": "腾讯云 SecretId", "access.form.tencentcloud_secret_id.placeholder": "请输入腾讯云 SecretId", "access.form.tencentcloud_secret_id.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/598/40488", @@ -147,20 +163,10 @@ "access.form.volcengine_secret_access_key.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6291/216571", "access.form.webhook_url.label": "Webhook 回调地址", "access.form.webhook_url.placeholder": "请输入 Webhook 回调地址", - "access.form.ssh_host.label": "服务器地址", - "access.form.ssh_host.placeholder": "请输入服务器地址", - "access.form.ssh_port.label": "服务器端口", - "access.form.ssh_port.placeholder": "请输入服务器端口", - "access.form.ssh_username.label": "用户名", - "access.form.ssh_username.placeholder": "请输入用户名", - "access.form.ssh_password.label": "密码", - "access.form.ssh_password.placeholder": "请输入密码", - "access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。
该字段与密钥文件字段二选一。", - "access.form.ssh_key.label": "SSH 密钥", - "access.form.ssh_key.placeholder": "请输入 SSH 密钥文件", - "access.form.ssh_key.upload": "选择文件", - "access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。
该字段与密码字段二选一。", - "access.form.ssh_key_passphrase.label": "SSH 密钥口令", - "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令", - "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。" + "access.form.westcn_username.label": "西部数码用户名", + "access.form.westcn_username.placeholder": "请输入西部数码用户名", + "access.form.westcn_username.tooltip": "这是什么?请参阅 https://www.west.cn/CustomerCenter/doc/apiv2.html", + "access.form.westcn_api_password.label": "西部数据 API 密码", + "access.form.westcn_api_password.placeholder": "请输入西部数据 API 密码", + "access.form.westcn_api_password.tooltip": "这是什么?请参阅 https://www.west.cn/CustomerCenter/doc/apiv2.html" } diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index feca7718..d555d473 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -93,6 +93,7 @@ "common.provider.volcengine.live": "火山引擎 - 视频直播 Live", "common.provider.volcengine.tos": "火山引擎 - 对象存储 TOS", "common.provider.webhook": "Webhook", + "common.provider.westcn": "西部数码", "common.notifier.bark": "Bark", "common.notifier.dingtalk": "钉钉", From 2965fb2b47650e5da89c6fa493edec10dbfd9548 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 16:01:04 +0800 Subject: [PATCH 03/11] feat: add rainyun applicant --- README.md | 1 + README_EN.md | 1 + internal/applicant/providers.go | 16 +++++ internal/domain/access.go | 4 ++ internal/domain/provider.go | 2 + .../lego-providers/rainyun/rainyun.go | 37 +++++++++++ ui/public/imgs/providers/rainyun.svg | 10 +++ ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormRainYunConfig.tsx | 61 +++++++++++++++++++ ui/src/domain/access.ts | 5 ++ ui/src/domain/provider.ts | 6 +- ui/src/i18n/locales/en/nls.access.json | 3 + ui/src/i18n/locales/en/nls.common.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 3 + ui/src/i18n/locales/zh/nls.common.json | 1 + 15 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go create mode 100644 ui/public/imgs/providers/rainyun.svg create mode 100644 ui/src/components/access/AccessFormRainYunConfig.tsx diff --git a/README.md b/README.md index 762af031..ebfc8cce 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ make local.run | [Name.com](https://www.name.com/) | | | [NameSilo](https://www.namesilo.com/) | | | [IBM NS1 Connect](https://www.ibm.com/cn-zh/products/ns1-connect/) | | +| [雨云](https://www.rainyun.com/) | | | [西部数码](https://www.west.cn/) | | | [PowerDNS](https://www.powerdns.com/) | | | ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 | diff --git a/README_EN.md b/README_EN.md index fbb97125..502062c6 100644 --- a/README_EN.md +++ b/README_EN.md @@ -98,6 +98,7 @@ The following DNS providers are supported: | [Name.com](https://www.name.com/) | | | [NameSilo](https://www.namesilo.com/) | | | [IBM NS1 Connect](https://www.ibm.com/products/ns1-connect/) | | +| [Rain Yun](https://www.rainyun.com/) | | | [West.cn](https://www.west.cn/) | | | [PowerDNS](https://www.powerdns.com/) | | | ACME Proxy HTTP Request | Supports managing DNS by HTTP request | diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 41541f01..75d9d3ce 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -17,6 +17,7 @@ import ( providerNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo" providerNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1" providerPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns" + providerRainYun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun" providerTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud" providerVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine" providerWestcn "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn" @@ -208,6 +209,21 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } + case domain.ApplyDNSProviderTypeRainYun: + { + access := domain.AccessConfigForRainYun{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) + } + + applicant, err := providerRainYun.NewChallengeProvider(&providerRainYun.RainYunApplicantConfig{ + ApiKey: access.ApiKey, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } + case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS: { access := domain.AccessConfigForTencentCloud{} diff --git a/internal/domain/access.go b/internal/domain/access.go index d045853a..7f49c897 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -112,6 +112,10 @@ type AccessConfigForQiniu struct { SecretKey string `json:"secretKey"` } +type AccessConfigForRainYun struct { + ApiKey string `json:"apiKey"` +} + type AccessConfigForSSH struct { Host string `json:"host"` Port int32 `json:"port"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 32835e6c..c780b934 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -27,6 +27,7 @@ const ( AccessProviderTypeNS1 = AccessProviderType("ns1") AccessProviderTypePowerDNS = AccessProviderType("powerdns") AccessProviderTypeQiniu = AccessProviderType("qiniu") + AccessProviderTypeRainYun = AccessProviderType("rainyun") AccessProviderTypeSSH = AccessProviderType("ssh") AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud") AccessProviderTypeUCloud = AccessProviderType("ucloud") @@ -59,6 +60,7 @@ const ( ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo") ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1") ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType("powerdns") + ApplyDNSProviderTypeRainYun = ApplyDNSProviderType("rainyun") ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType("tencentcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS] ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns") ApplyDNSProviderTypeVolcEngine = ApplyDNSProviderType("volcengine") // 兼容旧值,等同于 [ApplyDNSProviderTypeVolcEngineDNS] diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go new file mode 100644 index 00000000..d250a279 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go @@ -0,0 +1,37 @@ +package rainyun + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/providers/dns/rainyun" +) + +type RainYunApplicantConfig struct { + ApiKey string `json:"apiKey"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *RainYunApplicantConfig) (challenge.Provider, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + providerConfig := rainyun.NewDefaultConfig() + providerConfig.APIKey = config.ApiKey + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := rainyun.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/rainyun.svg b/ui/public/imgs/providers/rainyun.svg new file mode 100644 index 00000000..18413e16 --- /dev/null +++ b/ui/public/imgs/providers/rainyun.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 296eec43..9671ffa2 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -27,6 +27,7 @@ import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig"; import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig"; import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig"; import AccessFormQiniuConfig from "./AccessFormQiniuConfig"; +import AccessFormRainYunConfig from "./AccessFormRainYunConfig"; import AccessFormSSHConfig from "./AccessFormSSHConfig"; import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig"; import AccessFormUCloudConfig from "./AccessFormUCloudConfig"; @@ -122,6 +123,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.QINIU: return ; + case ACCESS_PROVIDERS.RAINYUN: + return ; case ACCESS_PROVIDERS.SSH: return ; case ACCESS_PROVIDERS.TENCENTCLOUD: diff --git a/ui/src/components/access/AccessFormRainYunConfig.tsx b/ui/src/components/access/AccessFormRainYunConfig.tsx new file mode 100644 index 00000000..a34cf683 --- /dev/null +++ b/ui/src/components/access/AccessFormRainYunConfig.tsx @@ -0,0 +1,61 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForRainYun } from "@/domain/access"; + +type AccessFormRainYunConfigFieldValues = Nullish; + +export type AccessFormRainYunConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormRainYunConfigFieldValues; + onValuesChange?: (values: AccessFormRainYunConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormRainYunConfigFieldValues => { + return { + apiKey: "", + }; +}; + +const AccessFormRainYunConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormRainYunConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiKey: z + .string() + .min(1, t("access.form.rainyun_api_key.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default AccessFormRainYunConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 19167ddc..8efe8b27 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -25,6 +25,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForNameSilo | AccessConfigForPowerDNS | AccessConfigForQiniu + | AccessConfigForRainYun | AccessConfigForSSH | AccessConfigForTencentCloud | AccessConfigForUCloud @@ -123,6 +124,10 @@ export type AccessConfigForQiniu = { secretKey: string; }; +export type AccessConfigForRainYun = { + apiKey: string; +}; + export type AccessConfigForSSH = { host: string; port: number; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index cb8acfb1..a13011be 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -22,6 +22,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ NS1: "ns1", POWERDNS: "powerdns", QINIU: "qiniu", + RAINYUN: "rainyun", SSH: "ssh", TENCENTCLOUD: "tencentcloud", UCLOUD: "ucloud", @@ -60,10 +61,10 @@ export const accessProvidersMap: Maphttps://portal.qiniu.com/", + "access.form.rainyun_api_key.label": "Rain Yun API key", + "access.form.rainyun_api_key.placeholder": "Please enter Rain Yun API key", + "access.form.rainyun_api_key.tooltip": "For more information, see https://www.rainyun.com/docs/account/racc/setting", "access.form.ssh_host.label": "Server host", "access.form.ssh_host.placeholder": "Please enter server host", "access.form.ssh_port.label": "Server port", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 3bed9469..7de04394 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -73,6 +73,7 @@ "common.provider.qiniu": "Qiniu", "common.provider.qiniu.cdn": "Qiniu - Content Delivery Network (CDN)", "common.provider.qiniu.pili": "Qiniu - Pili", + "common.provider.rainyun": "Rain Yun", "common.provider.ssh": "SSH deployment", "common.provider.tencentcloud": "Tencent Cloud", "common.provider.tencentcloud.cdn": "Tencent Cloud - Content Delivery Network (CDN)", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index d3baaf67..8be0c1cc 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -124,6 +124,9 @@ "access.form.qiniu_secret_key.label": "七牛云 SecretKey", "access.form.qiniu_secret_key.placeholder": "请输入七牛云 SecretKey", "access.form.qiniu_secret_key.tooltip": "这是什么?请参阅 https://portal.qiniu.com/", + "access.form.rainyun_api_key.label": "雨云 API 密钥", + "access.form.rainyun_api_key.placeholder": "请输入雨云 API 密钥", + "access.form.rainyun_api_key.tooltip": "这是什么?请参阅 https://www.rainyun.com/docs/account/racc/setting", "access.form.ssh_host.label": "服务器地址", "access.form.ssh_host.placeholder": "请输入服务器地址", "access.form.ssh_port.label": "服务器端口", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index d555d473..48fa3bbc 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -73,6 +73,7 @@ "common.provider.qiniu": "七牛云", "common.provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN", "common.provider.qiniu.pili": "七牛云 - 视频直播 Pili", + "common.provider.rainyun": "雨云", "common.provider.ssh": "SSH 部署", "common.provider.tencentcloud": "腾讯云", "common.provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN", From 2a7be1b24d3d92d1ddf1c6298b5c6f2285d638c2 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 17:09:05 +0800 Subject: [PATCH 04/11] feat: add aliyun waf deployer --- README.md | 32 ++-- README_EN.md | 32 ++-- go.mod | 2 + go.sum | 8 + internal/deployer/providers.go | 12 +- internal/domain/provider.go | 1 + .../providers/aliyun-alb/aliyun_alb.go | 38 +++-- .../providers/aliyun-nlb/aliyun_nlb.go | 38 +++-- .../providers/aliyun-waf/aliyun_waf.go | 150 ++++++++++++++++++ .../providers/aliyun-waf/aliyun_waf_test.go | 80 ++++++++++ .../providers/qiniu-pili/qiniu_pili_test.go | 2 +- .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../DeployNodeConfigFormAliyunWAFConfig.tsx | 79 +++++++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.common.json | 55 +++---- .../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 + 18 files changed, 454 insertions(+), 93 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx diff --git a/README.md b/README.md index ebfc8cce..0c9b73a2 100644 --- a/README.md +++ b/README.md @@ -114,22 +114,22 @@ make local.run [展开查看] -| 提供商 | 备注 | -| :-------------------------------------- | :------------------------------------------------------------- | -| 本地部署 | 可部署到本地服务器 | -| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP) | -| Webhook 回调 | 可部署到 Webhook | -| [Kubernetes](https://kubernetes.io/) | 可部署到 Kubernetes Secret | -| [阿里云](https://www.aliyun.com/) | 可部署到阿里云 OSS、CDN、DCDN、SLB(CLB/ALB/NLB)、Live 等服务 | -| [腾讯云](https://cloud.tencent.com/) | 可部署到腾讯云 COS、CDN、ECDN、EdgeOne、CLB、CSS 等服务 | -| [百度智能云](https://cloud.baidu.com/) | 可部署到百度智能云 CDN 等服务 | -| [华为云](https://www.huaweicloud.com/) | 可部署到华为云 CDN、ELB 等服务 | -| [火山引擎](https://www.volcengine.com/) | 可部署到火山引擎 TOS、CDN、DCDN、CLB、Live 等服务 | -| [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN、直播云等服务 | -| [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN | -| [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 | -| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 | -| [Edgio](https://edg.io/) | 可部署到 Edgio Applications 等服务 | +| 提供商 | 备注 | +| :-------------------------------------- | :------------------------------------------------------------------ | +| 本地部署 | 可部署到本地服务器 | +| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP) | +| Webhook 回调 | 可部署到 Webhook | +| [Kubernetes](https://kubernetes.io/) | 可部署到 Kubernetes Secret | +| [阿里云](https://www.aliyun.com/) | 可部署到阿里云 OSS、CDN、DCDN、SLB(CLB/ALB/NLB)、WAF、Live 等服务 | +| [腾讯云](https://cloud.tencent.com/) | 可部署到腾讯云 COS、CDN、ECDN、EdgeOne、CLB、CSS 等服务 | +| [百度智能云](https://cloud.baidu.com/) | 可部署到百度智能云 CDN 等服务 | +| [华为云](https://www.huaweicloud.com/) | 可部署到华为云 CDN、ELB 等服务 | +| [火山引擎](https://www.volcengine.com/) | 可部署到火山引擎 TOS、CDN、DCDN、CLB、Live 等服务 | +| [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN、直播云等服务 | +| [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN | +| [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 | +| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 | +| [Edgio](https://edg.io/) | 可部署到 Edgio Applications 等服务 | diff --git a/README_EN.md b/README_EN.md index 502062c6..b915cb95 100644 --- a/README_EN.md +++ b/README_EN.md @@ -113,22 +113,22 @@ The following hosting providers are supported: [Fold/Unfold to view ...] -| Provider | Remarks | -| :---------------------------------------------- | :-------------------------------------------------------------------------- | -| Local | Supports deployment to local servers | -| SSH | Supports deployment to remote servers (via SSH+SFTP) | -| Webhook | Supports deployment to Webhook | -| [Kubernetes](https://kubernetes.io/) | Supports deployment to Kubernetes Secret | -| [Alibaba Cloud](https://www.alibabacloud.com/) | Supports deployment to Alibaba Cloud OSS, CDN, DCDN, SLB(CLB/ALB/NLB), Live | -| [Tencent Cloud](https://www.tencentcloud.com/) | Supports deployment to Tencent Cloud COS, CDN, ECDN, EdgeOne, CLB, CSS | -| [Baidu AI Cloud](https://intl.cloud.baidu.com/) | Supports deployment to Baidu AI CLoud CDN | -| [Huawei Cloud](https://www.huaweicloud.com/) | Supports deployment to Huawei Cloud CDN, ELB | -| [Volcengine](https://www.volcengine.com/) | Supports deployment to Volcengine TOS, CDN, DCDN, CLB, Live | -| [Qiniu Cloud](https://www.qiniu.com/) | Supports deployment to Qiniu Cloud CDN, Pili | -| [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN | -| [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN | -| [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN | -| [Edgio](https://edg.io/) | Supports deployment to Edgio Applications | +| Provider | Remarks | +| :---------------------------------------------- | :------------------------------------------------------------------------------- | +| Local | Supports deployment to local servers | +| SSH | Supports deployment to remote servers (via SSH+SFTP) | +| Webhook | Supports deployment to Webhook | +| [Kubernetes](https://kubernetes.io/) | Supports deployment to Kubernetes Secret | +| [Alibaba Cloud](https://www.alibabacloud.com/) | Supports deployment to Alibaba Cloud OSS, CDN, DCDN, SLB(CLB/ALB/NLB), WAF, Live | +| [Tencent Cloud](https://www.tencentcloud.com/) | Supports deployment to Tencent Cloud COS, CDN, ECDN, EdgeOne, CLB, CSS | +| [Baidu AI Cloud](https://intl.cloud.baidu.com/) | Supports deployment to Baidu AI CLoud CDN | +| [Huawei Cloud](https://www.huaweicloud.com/) | Supports deployment to Huawei Cloud CDN, ELB | +| [Volcengine](https://www.volcengine.com/) | Supports deployment to Volcengine TOS, CDN, DCDN, CLB, Live | +| [Qiniu Cloud](https://www.qiniu.com/) | Supports deployment to Qiniu Cloud CDN, Pili | +| [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN | +| [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN | +| [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN | +| [Edgio](https://edg.io/) | Supports deployment to Edgio Applications | diff --git a/go.mod b/go.mod index 04da51d9..2834a7cc 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,8 @@ require ( github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect + github.com/alibabacloud-go/waf-openapi-20211001 v1.0.0 // indirect + github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4 // indirect github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1 // indirect github.com/blinkbean/dingtalk v1.1.3 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect diff --git a/go.sum b/go.sum index 831e78c0..570c46ab 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,7 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi v0.1.18/go.mod h1:PB4HffMhJVmAgNKNq3wYbTUlFvPgxJpTzd1F5pTuUsc= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.5/go.mod h1:kUe8JqFmoVU7lfBauaDD5taFaW7mBI+xVsyHutYtabg= @@ -130,6 +131,7 @@ github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= +github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0 h1:EQmKhYju6y38kJ1ZvZROeJG2Q1Wk6hlc8KQrVhvGyaw= @@ -173,6 +175,7 @@ github.com/alibabacloud-go/tea-oss-utils v1.1.0 h1:y65crjjcZ2Pbb6UZtC2deuIZHDVTS github.com/alibabacloud-go/tea-oss-utils v1.1.0/go.mod h1:PFCF12e9yEKyBUIn7X1IrF/pNjvxgkHy0CgxX4+xRuY= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils v1.3.6/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA= github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= @@ -186,6 +189,10 @@ github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCE github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= +github.com/alibabacloud-go/waf-openapi-20211001 v1.0.0 h1:CJ2vCd/wy3AVDIEkJdD5TJ7urzbbu9+9ruQ9V+WunN4= +github.com/alibabacloud-go/waf-openapi-20211001 v1.0.0/go.mod h1:UJvk4Yr8upLmocsvWY1GYJGCQ41A8ea8tfaRqV0itBY= +github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4 h1:Od0KgA73DyG9X2XFwuZZTkDv2pzA6B5mhYapyyca6QE= +github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4/go.mod h1:DohGoS8BnMxHXghHebtjPP7+GMdxPsRN19T3nn2HcCU= github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 h1:YBkf7H5CSgrlb3C1aWcpDt7Vk8UEGFPeD2OOirtt6IM= github.com/aliyun/alibaba-cloud-sdk-go v1.63.83/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= @@ -284,6 +291,7 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/clbanning/mxj/v2 v2.5.6/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 4b525b47..fb4d10c6 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -12,6 +12,7 @@ import ( 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" + providerAliyunWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf" providerBaiduCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn" providerBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn" providerDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" @@ -49,7 +50,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, NOTICE: If you add new constant, please keep ASCII order. */ switch options.Provider { - case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS: + case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunWAF: { access := domain.AccessConfigForAliyun{} if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { @@ -127,6 +128,15 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, }, logger) return deployer, logger, err + case domain.DeployProviderTypeAliyunWAF: + deployer, err := providerAliyunWAF.NewWithLogger(&providerAliyunWAF.AliyunWAFDeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + InstanceId: maps.GetValueAsString(options.ProviderDeployConfig, "instanceId"), + }, logger) + return deployer, logger, err + default: break } diff --git a/internal/domain/provider.go b/internal/domain/provider.go index c780b934..ea950bbd 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -85,6 +85,7 @@ const ( DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live") DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb") DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss") + DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf") DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn") DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn") DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn") diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go index 355f4c0b..072f4a74 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -73,22 +73,7 @@ func NewWithLogger(config *AliyunALBDeployerConfig, logger logger.Logger) (*Aliy return nil, xerrors.Wrap(err, "failed to create sdk clients") } - aliyunCasRegion := config.Region - if aliyunCasRegion != "" { - // 阿里云 CAS 服务接入点是独立于 ALB 服务的 - // 国内版固定接入点:华东一杭州 - // 国际版固定接入点:亚太东南一新加坡 - if !strings.HasPrefix(aliyunCasRegion, "cn-") { - aliyunCasRegion = "ap-southeast-1" - } else { - aliyunCasRegion = "cn-hangzhou" - } - } - uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ - AccessKeyId: config.AccessKeyId, - AccessKeySecret: config.AccessKeySecret, - Region: aliyunCasRegion, - }) + uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region) if err != nil { return nil, xerrors.Wrap(err, "failed to create ssl uploader") } @@ -446,3 +431,24 @@ func createSdkClients(accessKeyId, accessKeySecret, region string) (*wSdkClients cas: casClient, }, nil } + +func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) { + casRegion := region + if casRegion != "" { + // 阿里云 CAS 服务接入点是独立于 ALB 服务的 + // 国内版固定接入点:华东一杭州 + // 国际版固定接入点:亚太东南一新加坡 + if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") { + casRegion = "ap-southeast-1" + } else { + casRegion = "cn-hangzhou" + } + } + + uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + Region: casRegion, + }) + return uploader, err +} diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go index 2b273bc2..35286919 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -61,22 +61,7 @@ func NewWithLogger(config *AliyunNLBDeployerConfig, logger logger.Logger) (*Aliy return nil, xerrors.Wrap(err, "failed to create sdk client") } - aliyunCasRegion := config.Region - if aliyunCasRegion != "" { - // 阿里云 CAS 服务接入点是独立于 NLB 服务的 - // 国内版固定接入点:华东一杭州 - // 国际版固定接入点:亚太东南一新加坡 - if !strings.HasPrefix(aliyunCasRegion, "cn-") { - aliyunCasRegion = "ap-southeast-1" - } else { - aliyunCasRegion = "cn-hangzhou" - } - } - uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ - AccessKeyId: config.AccessKeyId, - AccessKeySecret: config.AccessKeySecret, - Region: aliyunCasRegion, - }) + uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region) if err != nil { return nil, xerrors.Wrap(err, "failed to create ssl uploader") } @@ -249,3 +234,24 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Cl return client, nil } + +func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) { + casRegion := region + if casRegion != "" { + // 阿里云 CAS 服务接入点是独立于 NLB 服务的 + // 国内版固定接入点:华东一杭州 + // 国际版固定接入点:亚太东南一新加坡 + if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") { + casRegion = "ap-southeast-1" + } else { + casRegion = "cn-hangzhou" + } + } + + uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + Region: casRegion, + }) + return uploader, err +} diff --git a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go new file mode 100644 index 00000000..58289fc2 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go @@ -0,0 +1,150 @@ +package aliyunwaf + +import ( + "context" + "errors" + "fmt" + "strings" + + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/tea" + aliyunWaf "github.com/alibabacloud-go/waf-openapi-20211001/v5/client" + 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" + providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" +) + +type AliyunWAFDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 阿里云 WAF 实例 ID。 + InstanceId string `json:"instanceId"` +} + +type AliyunWAFDeployer struct { + config *AliyunWAFDeployerConfig + logger logger.Logger + sdkClient *aliyunWaf.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AliyunWAFDeployer)(nil) + +func New(config *AliyunWAFDeployerConfig) (*AliyunWAFDeployer, error) { + return NewWithLogger(config, logger.NewNilLogger()) +} + +func NewWithLogger(config *AliyunWAFDeployerConfig, logger logger.Logger) (*AliyunWAFDeployer, 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 &AliyunWAFDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunWAFDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.InstanceId == "" { + return nil, errors.New("config `instanceId` 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) + + // 查询默认 SSL/TLS 设置 + // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-describedefaulthttps + describeDefaultHttpsReq := &aliyunWaf.DescribeDefaultHttpsRequest{ + InstanceId: tea.String(d.config.InstanceId), + RegionId: tea.String(d.config.Region), + } + describeDefaultHttpsResp, err := d.sdkClient.DescribeDefaultHttps(describeDefaultHttpsReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.DescribeDefaultHttps'") + } + + d.logger.Logt("已查询到默认 SSL/TLS 设置", describeDefaultHttpsResp) + + // 修改默认 SSL/TLS 设置 + // REF: https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-modifydefaulthttps + modifyDefaultHttpsReq := &aliyunWaf.ModifyDefaultHttpsRequest{ + InstanceId: tea.String(d.config.InstanceId), + RegionId: tea.String(d.config.Region), + CertId: tea.String(upres.CertId), + TLSVersion: describeDefaultHttpsResp.Body.DefaultHttps.TLSVersion, + EnableTLSv3: describeDefaultHttpsResp.Body.DefaultHttps.EnableTLSv3, + } + modifyDefaultHttpsResp, err := d.sdkClient.ModifyDefaultHttps(modifyDefaultHttpsReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'waf.ModifyDefaultHttps'") + } + + d.logger.Logt("已修改默认 SSL/TLS 设置", modifyDefaultHttpsResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunWaf.Client, error) { + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(fmt.Sprintf("wafopenapi.%s.aliyuncs.com", region)), + } + + client, err := aliyunWaf.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 服务接入点是独立于 WAF 服务的 + // 国内版固定接入点:华东一杭州 + // 国际版固定接入点:亚太东南一新加坡 + if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") { + casRegion = "ap-southeast-1" + } else { + casRegion = "cn-hangzhou" + } + } + + uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + Region: casRegion, + }) + return uploader, err +} diff --git a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go new file mode 100644 index 00000000..2498beca --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go @@ -0,0 +1,80 @@ +package aliyunwaf_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fInstanceId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNWAF_" + + 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.StringVar(&fInstanceId, argsPrefix+"INSTANCEID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./aliyun_waf_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNWAF_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNWAF_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNWAF_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNWAF_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNWAF_INSTANCEID="your-waf-instance-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("INSTANCEID: %v", fInstanceId), + }, "\n")) + + deployer, err := provider.New(&provider.AliyunWAFDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + InstanceId: fInstanceId, + }) + 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/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go index 86449ce2..06ef47e4 100644 --- a/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go +++ b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili_test.go @@ -34,7 +34,7 @@ func init() { /* Shell command to run this test: - go test -v ./qiniu_cdn_test.go -args \ + go test -v ./qiniu_pili_test.go -args \ --CERTIMATE_DEPLOYER_QINIUPILI_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_DEPLOYER_QINIUPILI_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_QINIUPILI_ACCESSKEY="your-access-key" \ diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index bac8b948..77d110c6 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -22,6 +22,7 @@ import DeployNodeConfigFormAliyunDCDNConfig from "./DeployNodeConfigFormAliyunDC import DeployNodeConfigFormAliyunLiveConfig from "./DeployNodeConfigFormAliyunLiveConfig"; import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLBConfig"; import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig"; +import DeployNodeConfigFormAliyunWAFConfig from "./DeployNodeConfigFormAliyunWAFConfig"; import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaiduCloudCDNConfig"; import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig"; import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig"; @@ -133,6 +134,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.ALIYUN_OSS: return ; + case DEPLOY_PROVIDERS.ALIYUN_WAF: + return ; case DEPLOY_PROVIDERS.BAIDUCLOUD_CDN: return ; case DEPLOY_PROVIDERS.BYTEPLUS_CDN: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx new file mode 100644 index 00000000..9794f9fd --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx @@ -0,0 +1,79 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormAliyunWAFConfigFieldValues = Nullish<{ + region: string; + instanceId: string; +}>; + +export type DeployNodeConfigFormAliyunWAFConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAliyunWAFConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAliyunWAFConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormAliyunWAFConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAliyunWAFConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormAliyunWAFConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.deploy.form.aliyun_waf_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aliyun_waf_region.placeholder")) + .trim(), + instanceId: z + .string({ message: t("workflow_node.deploy.form.aliyun_instance_id.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aliyun_instance_id.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default DeployNodeConfigFormAliyunWAFConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index a13011be..b6004053 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -174,6 +174,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({ ALIYUN_LIVE: `${ACCESS_PROVIDERS.ALIYUN}-live`, ALIYUN_NLB: `${ACCESS_PROVIDERS.ALIYUN}-nlb`, ALIYUN_OSS: `${ACCESS_PROVIDERS.ALIYUN}-oss`, + ALIYUN_WAF: `${ACCESS_PROVIDERS.ALIYUN}-waf`, BAIDUCLOUD_CDN: `${ACCESS_PROVIDERS.BAIDUCLOUD}-cdn`, BYTEPLUS_CDN: `${ACCESS_PROVIDERS.BYTEPLUS}-cdn`, DOGECLOUD_CDN: `${ACCESS_PROVIDERS.DOGECLOUD}-cdn`, @@ -226,6 +227,7 @@ export const deployProvidersMap: Maphttps://oss.console.aliyun.com", + "workflow_node.deploy.form.aliyun_waf_region.label": "Alibaba Cloud region", + "workflow_node.deploy.form.aliyun_waf_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_waf_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint", + "workflow_node.deploy.form.aliyun_waf_instance_id.label": "Alibaba Cloud WAF instance ID", + "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "Please enter Alibaba Cloud WAF instance ID", + "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "For more information, see https://waf.console.aliyun.com", "workflow_node.deploy.form.baiducloud_cdn_domain.label": "Baidu Cloud CDN domain", "workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "Please enter Baidu Cloud CDN domain name", "workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "For more information, see https://console.bce.baidu.com/cdn", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 48fa3bbc..e7e742b8 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -45,6 +45,7 @@ "common.provider.aliyun.live": "阿里云 - 视频直播 Live", "common.provider.aliyun.nlb": "阿里云 - 网络型负载均衡 NLB", "common.provider.aliyun.oss": "阿里云 - 对象存储 OSS", + "common.provider.aliyun.waf": "阿里云 - Web 应用防火墙 WAF", "common.provider.aws": "AWS", "common.provider.aws.route53": "AWS - Route53", "common.provider.azure": "Azure", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 144c0b58..2d43d042 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -153,6 +153,12 @@ "workflow_node.deploy.form.aliyun_oss_domain.label": "阿里云 OSS 自定义域名", "workflow_node.deploy.form.aliyun_oss_domain.placeholder": "请输入阿里云 OSS 自定义域名", "workflow_node.deploy.form.aliyun_oss_domain.tooltip": "这是什么?请参阅 see https://oss.console.aliyun.com", + "workflow_node.deploy.form.aliyun_waf_region.label": "阿里云地域", + "workflow_node.deploy.form.aliyun_waf_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_waf_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint", + "workflow_node.deploy.form.aliyun_waf_instance_id.label": "阿里云 WAF 实例 ID", + "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "请输入阿里云 WAF 实例 ID", + "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 https://waf.console.aliyun.com", "workflow_node.deploy.form.baiducloud_cdn_domain.label": "百度智能云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "请输入百度智能云 CDN 加速域名", "workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/cdn

泛域名表示形式为:*.example.com", From 9370f9d68f9ed608707db327be4d77bb160cd48c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 17:53:19 +0800 Subject: [PATCH 05/11] feat: add cloudns applicant --- README.md | 1 + README_EN.md | 1 + internal/applicant/providers.go | 17 ++++ internal/domain/access.go | 5 + internal/domain/provider.go | 2 + .../lego-providers/cloudns/cloudns.go | 39 ++++++++ ui/.eslintrc.cjs | 1 + ui/public/imgs/providers/cloudns.svg | 99 +++++++++++++++++++ ui/src/components/access/AccessForm.tsx | 7 +- .../access/AccessFormClouDNSConfig.tsx | 76 ++++++++++++++ .../notification/NotifyChannelEditForm.tsx | 2 +- .../components/workflow/WorkflowElement.tsx | 1 - ui/src/components/workflow/node/ApplyNode.tsx | 2 +- .../workflow/node/ApplyNodeConfigForm.tsx | 4 +- .../workflow/node/ConditionNode.tsx | 2 +- .../components/workflow/node/DeployNode.tsx | 2 +- .../workflow/node/DeployNodeConfigForm.tsx | 2 +- .../workflow/node/ExecuteResultNode.tsx | 2 +- .../components/workflow/node/NotifyNode.tsx | 2 +- ui/src/components/workflow/node/StartNode.tsx | 2 +- .../components/workflow/node/UploadNode.tsx | 2 +- ui/src/domain/access.ts | 6 ++ ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 6 ++ ui/src/i18n/locales/en/nls.common.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 6 ++ ui/src/i18n/locales/zh/nls.common.json | 1 + ui/src/router.tsx | 4 +- 28 files changed, 283 insertions(+), 16 deletions(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns/cloudns.go create mode 100644 ui/public/imgs/providers/cloudns.svg create mode 100644 ui/src/components/access/AccessFormClouDNSConfig.tsx diff --git a/README.md b/README.md index 0c9b73a2..5aea2374 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ make local.run | [AWS Route53](https://aws.amazon.com/route53/) | | | [Azure](https://azure.microsoft.com/) | | | [CloudFlare](https://www.cloudflare.com/) | | +| [ClouDNS](https://www.cloudns.net//) | | | [GoDaddy](https://www.godaddy.com/) | | | [Name.com](https://www.name.com/) | | | [NameSilo](https://www.namesilo.com/) | | diff --git a/README_EN.md b/README_EN.md index b915cb95..c360ce63 100644 --- a/README_EN.md +++ b/README_EN.md @@ -94,6 +94,7 @@ The following DNS providers are supported: | [AWS Route53](https://aws.amazon.com/route53/) | | | [Azure DNS](https://azure.microsoft.com/) | | | [CloudFlare](https://www.cloudflare.com/) | | +| [ClouDNS](https://www.cloudns.net//) | | | [GoDaddy](https://www.godaddy.com/) | | | [Name.com](https://www.name.com/) | | | [NameSilo](https://www.namesilo.com/) | | diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 75d9d3ce..8329ff54 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -11,6 +11,7 @@ import ( providerAWSRoute53 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aws-route53" providerAzureDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns" providerCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare" + providerClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns" providerGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy" providerHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud" providerNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom" @@ -114,6 +115,22 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } + case domain.ApplyDNSProviderTypeClouDNS: + { + access := domain.AccessConfigForClouDNS{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) + } + + applicant, err := providerClouDNS.NewChallengeProvider(&providerClouDNS.ClouDNSApplicantConfig{ + AuthId: access.AuthId, + AuthPassword: access.AuthPassword, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } + case domain.ApplyDNSProviderTypeGoDaddy: { access := domain.AccessConfigForGoDaddy{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 7f49c897..70128622 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -63,6 +63,11 @@ type AccessConfigForCloudflare struct { DnsApiToken string `json:"dnsApiToken"` } +type AccessConfigForClouDNS struct { + AuthId string `json:"authId"` + AuthPassword string `json:"authPassword"` +} + type AccessConfigForDogeCloud struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index ea950bbd..71e048b3 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -16,6 +16,7 @@ const ( AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud") AccessProviderTypeBytePlus = AccessProviderType("byteplus") AccessProviderTypeCloudflare = AccessProviderType("cloudflare") + AccessProviderTypeClouDNS = AccessProviderType("cloudns") AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") AccessProviderTypeEdgio = AccessProviderType("edgio") AccessProviderTypeGoDaddy = AccessProviderType("godaddy") @@ -53,6 +54,7 @@ const ( ApplyDNSProviderTypeAWSRoute53 = ApplyDNSProviderType("aws-route53") ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns") ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare") + ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns") ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy") ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType("huaweicloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS] ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns") diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns/cloudns.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns/cloudns.go new file mode 100644 index 00000000..09aac6df --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns/cloudns.go @@ -0,0 +1,39 @@ +package cloudns + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/providers/dns/cloudns" +) + +type ClouDNSApplicantConfig struct { + AuthId string `json:"authId"` + AuthPassword string `json:"authPassword"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ClouDNSApplicantConfig) (challenge.Provider, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + providerConfig := cloudns.NewDefaultConfig() + providerConfig.AuthID = config.AuthId + providerConfig.AuthPassword = config.AuthPassword + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := cloudns.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/.eslintrc.cjs b/ui/.eslintrc.cjs index 6bd9b5fd..d82915f5 100644 --- a/ui/.eslintrc.cjs +++ b/ui/.eslintrc.cjs @@ -84,6 +84,7 @@ module.exports = { pathGroupsExcludedImportTypes: ["builtin"], alphabetize: { order: "asc", + caseInsensitive: true, }, }, ], diff --git a/ui/public/imgs/providers/cloudns.svg b/ui/public/imgs/providers/cloudns.svg new file mode 100644 index 00000000..44cf1ed2 --- /dev/null +++ b/ui/public/imgs/providers/cloudns.svg @@ -0,0 +1,99 @@ + + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 9671ffa2..e007eb89 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -10,21 +10,22 @@ import { ACCESS_PROVIDERS } from "@/domain/provider"; import { useAntdForm, useAntdFormName } from "@/hooks"; import AccessFormACMEHttpReqConfig from "./AccessFormACMEHttpReqConfig"; -import AccessFormAWSConfig from "./AccessFormAWSConfig"; import AccessFormAliyunConfig from "./AccessFormAliyunConfig"; +import AccessFormAWSConfig from "./AccessFormAWSConfig"; import AccessFormAzureConfig from "./AccessFormAzureConfig"; import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig"; import AccessFormBytePlusConfig from "./AccessFormBytePlusConfig"; import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig"; +import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig"; import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig"; import AccessFormEdgioConfig from "./AccessFormEdgioConfig"; import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig"; import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig"; import AccessFormLocalConfig from "./AccessFormLocalConfig"; -import AccessFormNS1Config from "./AccessFormNS1Config"; import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig"; import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig"; +import AccessFormNS1Config from "./AccessFormNS1Config"; import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig"; import AccessFormQiniuConfig from "./AccessFormQiniuConfig"; import AccessFormRainYunConfig from "./AccessFormRainYunConfig"; @@ -101,6 +102,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.CLOUDFLARE: return ; + case ACCESS_PROVIDERS.CLOUDNS: + return ; case ACCESS_PROVIDERS.DOGECLOUD: return ; case ACCESS_PROVIDERS.GODADDY: diff --git a/ui/src/components/access/AccessFormClouDNSConfig.tsx b/ui/src/components/access/AccessFormClouDNSConfig.tsx new file mode 100644 index 00000000..4472eeea --- /dev/null +++ b/ui/src/components/access/AccessFormClouDNSConfig.tsx @@ -0,0 +1,76 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForClouDNS } from "@/domain/access"; + +type AccessFormClouDNSConfigFieldValues = Nullish; + +export type AccessFormClouDNSConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormClouDNSConfigFieldValues; + onValuesChange?: (values: AccessFormClouDNSConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormClouDNSConfigFieldValues => { + return { + authId: "", + authPassword: "", + }; +}; + +const AccessFormClouDNSConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormClouDNSConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + authId: z + .string() + .trim() + .min(1, t("access.form.cloudns_auth_id.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })), + authPassword: z + .string() + .min(1, t("access.form.cloudns_auth_password.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormClouDNSConfig; diff --git a/ui/src/components/notification/NotifyChannelEditForm.tsx b/ui/src/components/notification/NotifyChannelEditForm.tsx index 2fa9b5b3..d818ee4c 100644 --- a/ui/src/components/notification/NotifyChannelEditForm.tsx +++ b/ui/src/components/notification/NotifyChannelEditForm.tsx @@ -10,8 +10,8 @@ import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields"; import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields"; import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields"; -import NotifyChannelEditFormWeComFields from "./NotifyChannelEditFormWeComFields"; import NotifyChannelEditFormWebhookFields from "./NotifyChannelEditFormWebhookFields"; +import NotifyChannelEditFormWeComFields from "./NotifyChannelEditFormWeComFields"; type NotifyChannelEditFormFieldValues = NotifyChannelsSettingsContent[keyof NotifyChannelsSettingsContent]; diff --git a/ui/src/components/workflow/WorkflowElement.tsx b/ui/src/components/workflow/WorkflowElement.tsx index d2272ef0..3aa70ff3 100644 --- a/ui/src/components/workflow/WorkflowElement.tsx +++ b/ui/src/components/workflow/WorkflowElement.tsx @@ -64,4 +64,3 @@ const WorkflowElement = ({ node, disabled, branchId, branchIndex }: WorkflowElem }; export default memo(WorkflowElement); - diff --git a/ui/src/components/workflow/node/ApplyNode.tsx b/ui/src/components/workflow/node/ApplyNode.tsx index 556188d9..6e090616 100644 --- a/ui/src/components/workflow/node/ApplyNode.tsx +++ b/ui/src/components/workflow/node/ApplyNode.tsx @@ -8,8 +8,8 @@ import { useZustandShallowSelector } from "@/hooks"; import { useContactEmailsStore } from "@/stores/contact"; import { useWorkflowStore } from "@/stores/workflow"; -import ApplyNodeConfigForm, { type ApplyNodeConfigFormInstance } from "./ApplyNodeConfigForm"; import SharedNode, { type SharedNodeProps } from "./_SharedNode"; +import ApplyNodeConfigForm, { type ApplyNodeConfigFormInstance } from "./ApplyNodeConfigForm"; export type ApplyNodeProps = SharedNodeProps; diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index 2280fbb8..1792163e 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -21,10 +21,10 @@ import { import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; -import ModalForm from "@/components/ModalForm"; -import MultipleInput from "@/components/MultipleInput"; import AccessEditModal from "@/components/access/AccessEditModal"; import AccessSelect from "@/components/access/AccessSelect"; +import ModalForm from "@/components/ModalForm"; +import MultipleInput from "@/components/MultipleInput"; import ApplyDNSProviderSelect from "@/components/provider/ApplyDNSProviderSelect"; import { ACCESS_USAGES, APPLY_DNS_PROVIDERS, accessProvidersMap, applyDNSProvidersMap } from "@/domain/provider"; import { type WorkflowNodeConfigForApply } from "@/domain/workflow"; diff --git a/ui/src/components/workflow/node/ConditionNode.tsx b/ui/src/components/workflow/node/ConditionNode.tsx index d84e8550..56639692 100644 --- a/ui/src/components/workflow/node/ConditionNode.tsx +++ b/ui/src/components/workflow/node/ConditionNode.tsx @@ -2,8 +2,8 @@ import { memo } from "react"; import { MoreOutlined as MoreOutlinedIcon } from "@ant-design/icons"; import { Button, Card, Popover } from "antd"; -import AddNode from "./AddNode"; import SharedNode, { type SharedNodeProps } from "./_SharedNode"; +import AddNode from "./AddNode"; export type ConditionNodeProps = SharedNodeProps & { branchId: string; diff --git a/ui/src/components/workflow/node/DeployNode.tsx b/ui/src/components/workflow/node/DeployNode.tsx index db5830da..9eca1248 100644 --- a/ui/src/components/workflow/node/DeployNode.tsx +++ b/ui/src/components/workflow/node/DeployNode.tsx @@ -8,8 +8,8 @@ import { type WorkflowNodeConfigForDeploy, WorkflowNodeType } from "@/domain/wor import { useZustandShallowSelector } from "@/hooks"; import { useWorkflowStore } from "@/stores/workflow"; -import DeployNodeConfigForm, { type DeployNodeConfigFormInstance } from "./DeployNodeConfigForm"; import SharedNode, { type SharedNodeProps } from "./_SharedNode"; +import DeployNodeConfigForm, { type DeployNodeConfigFormInstance } from "./DeployNodeConfigForm"; export type DeployNodeProps = SharedNodeProps; diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 77d110c6..3d85e6de 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -5,11 +5,11 @@ import { Alert, Button, Divider, Flex, Form, type FormInstance, Select, Switch, import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; -import Show from "@/components/Show"; import AccessEditModal from "@/components/access/AccessEditModal"; import AccessSelect from "@/components/access/AccessSelect"; import DeployProviderPicker from "@/components/provider/DeployProviderPicker"; import DeployProviderSelect from "@/components/provider/DeployProviderSelect"; +import Show from "@/components/Show"; import { ACCESS_USAGES, DEPLOY_PROVIDERS, accessProvidersMap, deployProvidersMap } from "@/domain/provider"; import { type WorkflowNode, type WorkflowNodeConfigForDeploy } from "@/domain/workflow"; import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks"; diff --git a/ui/src/components/workflow/node/ExecuteResultNode.tsx b/ui/src/components/workflow/node/ExecuteResultNode.tsx index b53b2a8c..69a0949c 100644 --- a/ui/src/components/workflow/node/ExecuteResultNode.tsx +++ b/ui/src/components/workflow/node/ExecuteResultNode.tsx @@ -8,8 +8,8 @@ import { import { Button, Card, Popover, theme } from "antd"; import { WorkflowNodeType } from "@/domain/workflow"; -import AddNode from "./AddNode"; import SharedNode, { type SharedNodeProps } from "./_SharedNode"; +import AddNode from "./AddNode"; export type ConditionNodeProps = SharedNodeProps & { branchId: string; diff --git a/ui/src/components/workflow/node/NotifyNode.tsx b/ui/src/components/workflow/node/NotifyNode.tsx index 1b739e8f..4ac35ab5 100644 --- a/ui/src/components/workflow/node/NotifyNode.tsx +++ b/ui/src/components/workflow/node/NotifyNode.tsx @@ -8,8 +8,8 @@ import { type WorkflowNodeConfigForNotify, WorkflowNodeType } from "@/domain/wor import { useZustandShallowSelector } from "@/hooks"; import { useWorkflowStore } from "@/stores/workflow"; -import NotifyNodeConfigForm, { type NotifyNodeConfigFormInstance } from "./NotifyNodeConfigForm"; import SharedNode, { type SharedNodeProps } from "./_SharedNode"; +import NotifyNodeConfigForm, { type NotifyNodeConfigFormInstance } from "./NotifyNodeConfigForm"; export type NotifyNodeProps = SharedNodeProps; diff --git a/ui/src/components/workflow/node/StartNode.tsx b/ui/src/components/workflow/node/StartNode.tsx index 1df65bc1..900793fa 100644 --- a/ui/src/components/workflow/node/StartNode.tsx +++ b/ui/src/components/workflow/node/StartNode.tsx @@ -7,8 +7,8 @@ import { WORKFLOW_TRIGGERS, type WorkflowNodeConfigForStart, WorkflowNodeType } import { useZustandShallowSelector } from "@/hooks"; import { useWorkflowStore } from "@/stores/workflow"; -import StartNodeConfigForm, { type StartNodeConfigFormInstance } from "./StartNodeConfigForm"; import SharedNode, { type SharedNodeProps } from "./_SharedNode"; +import StartNodeConfigForm, { type StartNodeConfigFormInstance } from "./StartNodeConfigForm"; export type StartNodeProps = SharedNodeProps; diff --git a/ui/src/components/workflow/node/UploadNode.tsx b/ui/src/components/workflow/node/UploadNode.tsx index 3d0e20c3..25850743 100644 --- a/ui/src/components/workflow/node/UploadNode.tsx +++ b/ui/src/components/workflow/node/UploadNode.tsx @@ -8,8 +8,8 @@ import { WorkflowNodeType } from "@/domain/workflow"; import { useZustandShallowSelector } from "@/hooks"; import { useWorkflowStore } from "@/stores/workflow"; -import UploadNodeConfigForm, { type UploadNodeConfigFormInstance } from "./UploadNodeConfigForm"; import SharedNode, { type SharedNodeProps } from "./_SharedNode"; +import UploadNodeConfigForm, { type UploadNodeConfigFormInstance } from "./UploadNodeConfigForm"; export type UploadNodeProps = SharedNodeProps; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 8efe8b27..14b31be6 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -15,6 +15,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForBaiduCloud | AccessConfigForBytePlus | AccessConfigForCloudflare + | AccessConfigForClouDNS | AccessConfigForDogeCloud | AccessConfigForEdgio | AccessConfigForGoDaddy @@ -75,6 +76,11 @@ export type AccessConfigForCloudflare = { dnsApiToken: string; }; +export type AccessConfigForClouDNS = { + authId: string; + authPassword: string; +}; + export type AccessConfigForDogeCloud = { accessKey: string; secretKey: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index b6004053..9a05d180 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -11,6 +11,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ BAIDUCLOUD: "baiducloud", BYTEPLUS: "byteplus", CLOUDFLARE: "cloudflare", + CLOUDNS: "cloudns", DOGECLOUD: "dogecloud", GODADDY: "godaddy", EDGIO: "edgio", @@ -71,6 +72,7 @@ export const accessProvidersMap: Maphttps://developers.cloudflare.com/fundamentals/api/get-started/create-token/", + "access.form.cloudns_auth_id.label": "ClouDNS API user ID", + "access.form.cloudns_auth_id.placeholder": "Please enter ClouDNS API user ID", + "access.form.cloudns_auth_id.tooltip": "For more information, see https://www.cloudns.net/wiki/article/42/", + "access.form.cloudns_auth_password.label": "ClouDNS API user password", + "access.form.cloudns_auth_password.placeholder": "Please enter ClouDNS API user password", + "access.form.cloudns_auth_password.tooltip": "For more information, see https://www.cloudns.net/wiki/article/42/", "access.form.dogecloud_access_key.label": "Doge Cloud AccessKey", "access.form.dogecloud_access_key.placeholder": "Please enter Doge Cloud AccessKey", "access.form.dogecloud_access_key.tooltip": "For more information, see https://console.dogecloud.com/", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 40c4837d..dbe271e7 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -55,6 +55,7 @@ "common.provider.byteplus": "BytePlus", "common.provider.byteplus.cdn": "BytePlus - CDN (Content Delivery Network)", "common.provider.cloudflare": "Cloudflare", + "common.provider.cloudns": "ClouDNS", "common.provider.dogecloud": "Doge Cloud", "common.provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)", "common.provider.edgio": "Edgio", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 8be0c1cc..7d51ad43 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -72,6 +72,12 @@ "access.form.cloudflare_dns_api_token.label": "Cloudflare API Token", "access.form.cloudflare_dns_api_token.placeholder": "请输入 Cloudflare API Token", "access.form.cloudflare_dns_api_token.tooltip": "这是什么?请参阅 https://developers.cloudflare.com/fundamentals/api/get-started/create-token/", + "access.form.cloudns_auth_id.label": "ClouDNS API 用户 ID", + "access.form.cloudns_auth_id.placeholder": "请输入 ClouDNS API 用户 ID", + "access.form.cloudns_auth_id.tooltip": "这是什么?请参阅 https://www.cloudns.net/wiki/article/42/", + "access.form.cloudns_auth_password.label": "ClouDNS API 用户密码", + "access.form.cloudns_auth_password.placeholder": "请输入 ClouDNS API 用户密码", + "access.form.cloudns_auth_password.tooltip": "这是什么?请参阅 https://www.cloudns.net/wiki/article/42/", "access.form.dogecloud_access_key.label": "多吉云 AccessKey", "access.form.dogecloud_access_key.placeholder": "请输入多吉云 AccessKey", "access.form.dogecloud_access_key.tooltip": "这是什么?请参阅 https://console.dogecloud.com/", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index e7e742b8..45072e74 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -55,6 +55,7 @@ "common.provider.byteplus": "BytePlus", "common.provider.byteplus.cdn": "BytePlus - 内容分发网络 CDN", "common.provider.cloudflare": "Cloudflare", + "common.provider.cloudns": "ClouDNS", "common.provider.dogecloud": "多吉云", "common.provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN", "common.provider.edgio": "Edgio", diff --git a/ui/src/router.tsx b/ui/src/router.tsx index 31265f6b..0bfa8b41 100644 --- a/ui/src/router.tsx +++ b/ui/src/router.tsx @@ -1,9 +1,9 @@ import { createHashRouter } from "react-router-dom"; -import AuthLayout from "./pages/AuthLayout"; -import ConsoleLayout from "./pages/ConsoleLayout"; import AccessList from "./pages/accesses/AccessList"; +import AuthLayout from "./pages/AuthLayout"; import CertificateList from "./pages/certificates/CertificateList"; +import ConsoleLayout from "./pages/ConsoleLayout"; import Dashboard from "./pages/dashboard/Dashboard"; import Login from "./pages/login/Login"; import Settings from "./pages/settings/Settings"; From 1651cda5b472655c4d6a877843cee117969b3747 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 21:55:05 +0800 Subject: [PATCH 06/11] feat: add aws acm uploader --- go.mod | 10 +- go.sum | 10 +- .../uploader/providers/aws-acm/aws_acm.go | 97 +++++++++++++++++++ .../providers/ucloud-ussl/ucloud_ussl.go | 4 +- internal/pkg/utils/certs/common.go | 4 + internal/pkg/utils/certs/converter.go | 25 +++++ 6 files changed, 137 insertions(+), 13 deletions(-) create mode 100644 internal/pkg/core/uploader/providers/aws-acm/aws_acm.go diff --git a/go.mod b/go.mod index 2834a7cc..7598eb2c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,9 @@ require ( github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 github.com/alibabacloud-go/slb-20140515/v4 v4.0.10 github.com/alibabacloud-go/tea v1.2.2 + github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4 github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible + github.com/aws/aws-sdk-go-v2/service/acm v1.30.13 github.com/baidubce/bce-sdk-go v0.9.214 github.com/byteplus-sdk/byteplus-sdk-golang v1.0.40 github.com/go-acme/lego/v4 v4.21.0 @@ -57,8 +59,6 @@ require ( github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect - github.com/alibabacloud-go/waf-openapi-20211001 v1.0.0 // indirect - github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4 // indirect github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1 // indirect github.com/blinkbean/dingtalk v1.1.3 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect @@ -118,10 +118,10 @@ require ( github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 // indirect github.com/aliyun/credentials-go v1.4.3 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go-v2 v1.33.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.33.0 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/config v1.29.1 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.54 // indirect + github.com/aws/aws-sdk-go-v2/config v1.29.1 + github.com/aws/aws-sdk-go-v2/credentials v1.17.54 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.52 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 // indirect diff --git a/go.sum b/go.sum index 570c46ab..c8111f99 100644 --- a/go.sum +++ b/go.sum @@ -123,7 +123,6 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= -github.com/alibabacloud-go/darabonba-openapi v0.1.18/go.mod h1:PB4HffMhJVmAgNKNq3wYbTUlFvPgxJpTzd1F5pTuUsc= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.5/go.mod h1:kUe8JqFmoVU7lfBauaDD5taFaW7mBI+xVsyHutYtabg= @@ -131,7 +130,6 @@ github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= -github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0 h1:EQmKhYju6y38kJ1ZvZROeJG2Q1Wk6hlc8KQrVhvGyaw= @@ -175,7 +173,6 @@ github.com/alibabacloud-go/tea-oss-utils v1.1.0 h1:y65crjjcZ2Pbb6UZtC2deuIZHDVTS github.com/alibabacloud-go/tea-oss-utils v1.1.0/go.mod h1:PFCF12e9yEKyBUIn7X1IrF/pNjvxgkHy0CgxX4+xRuY= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils v1.3.6/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= -github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA= github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= @@ -189,8 +186,6 @@ github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCE github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= -github.com/alibabacloud-go/waf-openapi-20211001 v1.0.0 h1:CJ2vCd/wy3AVDIEkJdD5TJ7urzbbu9+9ruQ9V+WunN4= -github.com/alibabacloud-go/waf-openapi-20211001 v1.0.0/go.mod h1:UJvk4Yr8upLmocsvWY1GYJGCQ41A8ea8tfaRqV0itBY= github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4 h1:Od0KgA73DyG9X2XFwuZZTkDv2pzA6B5mhYapyyca6QE= github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4/go.mod h1:DohGoS8BnMxHXghHebtjPP7+GMdxPsRN19T3nn2HcCU= github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 h1:YBkf7H5CSgrlb3C1aWcpDt7Vk8UEGFPeD2OOirtt6IM= @@ -237,6 +232,8 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvK github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28 h1:7kpeALOUeThs2kEjlAxlADAVfxKmkYAedlpZ3kdoSJ4= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28/go.mod h1:pyaOYEdp1MJWgtXLy6q80r3DhsVdOIOZNB9hdTcJIvI= +github.com/aws/aws-sdk-go-v2/service/acm v1.30.13 h1:aPCPsgDxQqOS3zPJKYJQVh02q8stjSQ1haHaUucCAUM= +github.com/aws/aws-sdk-go-v2/service/acm v1.30.13/go.mod h1:3pfuOCVLzWu3aiavTB9bOIdZpVadNYt6fyZdp+fDOSU= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= @@ -291,7 +288,6 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= -github.com/clbanning/mxj/v2 v2.5.6/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -396,6 +392,8 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= diff --git a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go new file mode 100644 index 00000000..e79ab6e7 --- /dev/null +++ b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go @@ -0,0 +1,97 @@ +package awsacm + +import ( + "context" + "errors" + "fmt" + "time" + + aws "github.com/aws/aws-sdk-go-v2/aws" + awsCfg "github.com/aws/aws-sdk-go-v2/config" + awsCred "github.com/aws/aws-sdk-go-v2/credentials" + awsAcm "github.com/aws/aws-sdk-go-v2/service/acm" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/uploader" + "github.com/usual2970/certimate/internal/pkg/utils/certs" +) + +type AWSCertificateManagerUploaderConfig struct { + // AWS AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // AWS SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // AWS 区域。 + Region string `json:"region"` +} + +type AWSCertificateManagerUploader struct { + config *AWSCertificateManagerUploaderConfig + sdkClient *awsAcm.Client +} + +var _ uploader.Uploader = (*AWSCertificateManagerUploader)(nil) + +func New(config *AWSCertificateManagerUploaderConfig) (*AWSCertificateManagerUploader, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &AWSCertificateManagerUploader{ + config: config, + sdkClient: client, + }, nil +} + +func (u *AWSCertificateManagerUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { + // 解析证书内容 + certX509, err := certs.ParseCertificateFromPEM(certPem) + if err != nil { + return nil, err + } + + // 生成 AWS 所需的服务端证书和证书链参数 + scertPem, _ := certs.ConvertCertificateToPEM(certX509) + bcertPem := certPem + + // 生成新证书名(需符合 AWS 命名规则) + var certId, certName string + certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli()) + + // 导入证书 + // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html + importCertificateReq := &awsAcm.ImportCertificateInput{ + Certificate: ([]byte)(scertPem), + CertificateChain: ([]byte)(bcertPem), + PrivateKey: ([]byte)(privkeyPem), + } + importCertificateResp, err := u.sdkClient.ImportCertificate(context.TODO(), importCertificateReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'acm.ImportCertificate'") + } + + certId = *importCertificateResp.CertificateArn + return &uploader.UploadResult{ + CertId: certId, + CertName: certName, + }, nil +} + +func createSdkClient(accessKeyId, secretAccessKey, region string) (*awsAcm.Client, error) { + cfg, err := awsCfg.LoadDefaultConfig(context.TODO()) + if err != nil { + return nil, err + } + + client := awsAcm.NewFromConfig(cfg, func(o *awsAcm.Options) { + o.Region = region + o.Credentials = aws.NewCredentialsCache(awsCred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, "")) + }) + + return client, nil +} diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go index 223c108e..9c5fa2b3 100644 --- a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go +++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go @@ -76,7 +76,7 @@ func (u *UCloudUSSLUploader) Upload(ctx context.Context, certPem string, privkey uploadNormalCertificateResp, err := u.sdkClient.UploadNormalCertificate(uploadNormalCertificateReq) if err != nil { if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 { - return u.getExistCert(ctx, certPem, privkeyPem) + return u.getExistCert(ctx, certPem) } return nil, xerrors.Wrap(err, "failed to execute sdk request 'ussl.UploadNormalCertificate'") @@ -92,7 +92,7 @@ func (u *UCloudUSSLUploader) Upload(ctx context.Context, certPem string, privkey }, nil } -func (u *UCloudUSSLUploader) getExistCert(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { +func (u *UCloudUSSLUploader) getExistCert(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) { // 解析证书内容 certX509, err := certs.ParseCertificateFromPEM(certPem) if err != nil { diff --git a/internal/pkg/utils/certs/common.go b/internal/pkg/utils/certs/common.go index fe5d041a..03fbeaeb 100644 --- a/internal/pkg/utils/certs/common.go +++ b/internal/pkg/utils/certs/common.go @@ -14,6 +14,10 @@ import ( // 出参: // - 是否相同。 func EqualCertificate(a, b *x509.Certificate) bool { + if a == nil || b == nil { + return false + } + return string(a.Signature) == string(b.Signature) && a.SignatureAlgorithm == b.SignatureAlgorithm && a.SerialNumber.String() == b.SerialNumber.String() && diff --git a/internal/pkg/utils/certs/converter.go b/internal/pkg/utils/certs/converter.go index a3272c45..eb13a358 100644 --- a/internal/pkg/utils/certs/converter.go +++ b/internal/pkg/utils/certs/converter.go @@ -8,6 +8,27 @@ import ( xerrors "github.com/pkg/errors" ) +// 将 x509.Certificate 对象转换为 PEM 编码的字符串。 +// +// 入参: +// - cert: x509.Certificate 对象。 +// +// 出参: +// - certPem: 证书 PEM 内容。 +// - err: 错误。 +func ConvertCertificateToPEM(cert *x509.Certificate) (certPem string, err error) { + if cert == nil { + return "", xerrors.New("cert is nil") + } + + block := &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + } + + return string(pem.EncodeToMemory(block)), nil +} + // 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。 // // 入参: @@ -17,6 +38,10 @@ import ( // - privkeyPem: 私钥 PEM 内容。 // - err: 错误。 func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (privkeyPem string, err error) { + if privkey == nil { + return "", xerrors.New("privkey is nil") + } + data, err := x509.MarshalECPrivateKey(privkey) if err != nil { return "", xerrors.Wrap(err, "failed to marshal EC private key") From 5ee5460612ca63b8b4795fa08a26d187612c3fc9 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 23:21:56 +0800 Subject: [PATCH 07/11] feat: add aws cloudfront deployer --- README.md | 1 + README_EN.md | 1 + go.mod | 1 + go.sum | 2 + internal/deployer/providers.go | 59 ++++++-- internal/domain/provider.go | 1 + .../acme-dns-01/lego-providers/gname/gname.go | 39 +++++ .../providers/aliyun-alb/aliyun_alb.go | 6 +- .../providers/aliyun-clb/aliyun_clb.go | 6 +- .../providers/aliyun-nlb/aliyun_nlb.go | 6 +- .../providers/aliyun-waf/aliyun_waf.go | 4 +- .../aws-cloudfront/aws_cloudfront.go | 133 ++++++++++++++++++ .../aws-cloudfront/aws_cloudfront_test.go | 80 +++++++++++ .../providers/byteplus-cdn/byteplus_cdn.go | 6 +- .../providers/dogecloud-cdn/dogecloud_cdn.go | 4 +- .../huaweicloud-cdn/huaweicloud_cdn.go | 4 +- .../huaweicloud-elb/huaweicloud_elb.go | 6 +- .../deployer/providers/qiniu-cdn/qiniu_cdn.go | 4 +- .../providers/qiniu-pili/qiniu_pili.go | 4 +- .../tencentcloud-cdn/tencentcloud_cdn.go | 4 +- .../tencentcloud-clb/tencentcloud_clb.go | 6 +- .../tencentcloud-cos/tencentcloud_cos.go | 4 +- .../tencentcloud-css/tencentcloud_css.go | 4 +- .../tencentcloud-ecdn/tencentcloud_ecdn.go | 4 +- .../tencentcloud-eo/tencentcloud_eo.go | 6 +- .../providers/ucloud-ucdn/ucloud_ucdn.go | 4 +- .../providers/ucloud-us3/ucloud_us3.go | 4 +- .../volcengine-cdn/volcengine_cdn.go | 6 +- .../volcengine-clb/volcengine_clb.go | 4 +- .../volcengine-dcdn/volcengine_dcdn.go | 4 +- .../volcengine-live/volcengine_live.go | 4 +- .../volcengine-tos/volcengine_tos.go | 4 +- .../uploader/providers/aws-acm/aws_acm.go | 1 - internal/pkg/utils/certs/converter.go | 5 +- .../workflow/node/DeployNodeConfigForm.tsx | 3 + ...eployNodeConfigFormAWSCloudFrontConfig.tsx | 79 +++++++++++ .../DeployNodeConfigFormAliyunWAFConfig.tsx | 4 +- ui/src/domain/provider.ts | 4 +- ui/src/i18n/locales/en/nls.common.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 10 +- ui/src/i18n/locales/zh/nls.common.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 10 +- 42 files changed, 467 insertions(+), 76 deletions(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go create mode 100644 internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go create mode 100644 internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront_test.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx diff --git a/README.md b/README.md index 5aea2374..28438c76 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ make local.run | [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN | | [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 | | [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 | +| [AWS](https://aws.amazon.com/) | 可部署到 AWS CloudFront 等服务 | | [Edgio](https://edg.io/) | 可部署到 Edgio Applications 等服务 | diff --git a/README_EN.md b/README_EN.md index c360ce63..5b3e67ff 100644 --- a/README_EN.md +++ b/README_EN.md @@ -129,6 +129,7 @@ The following hosting providers are supported: | [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN | | [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN | | [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN | +| [AWS](https://aws.amazon.com/) | Supports deployment to AWS CloudFront | | [Edgio](https://edg.io/) | Supports deployment to Edgio Applications | diff --git a/go.mod b/go.mod index 7598eb2c..3bb9f1d9 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.4 github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible github.com/aws/aws-sdk-go-v2/service/acm v1.30.13 + github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5 github.com/baidubce/bce-sdk-go v0.9.214 github.com/byteplus-sdk/byteplus-sdk-golang v1.0.40 github.com/go-acme/lego/v4 v4.21.0 diff --git a/go.sum b/go.sum index c8111f99..2111474e 100644 --- a/go.sum +++ b/go.sum @@ -234,6 +234,8 @@ github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28 h1:7kpeALOUeThs2kEjlAxlADAVfxK github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28/go.mod h1:pyaOYEdp1MJWgtXLy6q80r3DhsVdOIOZNB9hdTcJIvI= github.com/aws/aws-sdk-go-v2/service/acm v1.30.13 h1:aPCPsgDxQqOS3zPJKYJQVh02q8stjSQ1haHaUucCAUM= github.com/aws/aws-sdk-go-v2/service/acm v1.30.13/go.mod h1:3pfuOCVLzWu3aiavTB9bOIdZpVadNYt6fyZdp+fDOSU= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5 h1:oBLlEuSL5G9W8M4GtEVdNi+xsQP+9lphVkbYf38Isgs= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.5/go.mod h1:H/t3dGwvHy2WJ+ZwyDBWva7ttsoxSxt5qC1OMcc0iJ0= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index fb4d10c6..28e4108c 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -13,6 +13,7 @@ import ( providerAliyunNLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" providerAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" providerAliyunWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf" + providerAWSCloudFront "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-cloudfront" providerBaiduCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn" providerBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn" providerDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" @@ -142,6 +143,28 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, } } + case domain.DeployProviderTypeAWSCloudFront: + { + access := domain.AccessConfigForAWS{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) + } + + switch options.Provider { + case domain.DeployProviderTypeAWSCloudFront: + deployer, err := providerAWSCloudFront.NewWithLogger(&providerAWSCloudFront.AWSCloudFrontDeployerConfig{ + AccessKeyId: access.AccessKeyId, + SecretAccessKey: access.SecretAccessKey, + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + DistributionId: maps.GetValueAsString(options.ProviderDeployConfig, "distributionId"), + }, logger) + return deployer, logger, err + + default: + break + } + } + case domain.DeployProviderTypeBaiduCloudCDN: { access := domain.AccessConfigForBaiduCloud{} @@ -149,12 +172,18 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } - deployer, err := providerBaiduCloudCDN.NewWithLogger(&providerBaiduCloudCDN.BaiduCloudCDNDeployerConfig{ - AccessKeyId: access.AccessKeyId, - SecretAccessKey: access.SecretAccessKey, - Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), - }, logger) - return deployer, logger, err + switch options.Provider { + case domain.DeployProviderTypeBaiduCloudCDN: + deployer, err := providerBaiduCloudCDN.NewWithLogger(&providerBaiduCloudCDN.BaiduCloudCDNDeployerConfig{ + AccessKeyId: access.AccessKeyId, + SecretAccessKey: access.SecretAccessKey, + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), + }, logger) + return deployer, logger, err + + default: + break + } } case domain.DeployProviderTypeBytePlusCDN: @@ -164,12 +193,18 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) } - deployer, err := providerBytePlusCDN.NewWithLogger(&providerBytePlusCDN.BytePlusCDNDeployerConfig{ - AccessKey: access.AccessKey, - SecretKey: access.SecretKey, - Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), - }, logger) - return deployer, logger, err + switch options.Provider { + case domain.DeployProviderTypeBytePlusCDN: + deployer, err := providerBytePlusCDN.NewWithLogger(&providerBytePlusCDN.BytePlusCDNDeployerConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), + }, logger) + return deployer, logger, err + + default: + break + } } case domain.DeployProviderTypeDogeCloudCDN: diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 71e048b3..374fab2c 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -88,6 +88,7 @@ const ( DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb") DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss") DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf") + DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront") DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn") DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn") DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn") diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go new file mode 100644 index 00000000..a4045997 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go @@ -0,0 +1,39 @@ +package gname + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/providers/dns/westcn" +) + +type GnameApplicantConfig struct { + Username string `json:"username"` + ApiPassword string `json:"apiPassword"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *GnameApplicantConfig) (challenge.Provider, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + providerConfig := westcn.NewDefaultConfig() + providerConfig.Username = config.Username + providerConfig.Password = config.ApiPassword + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := westcn.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go index 072f4a74..9878b6f4 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -18,7 +18,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" ) type AliyunALBDeployerConfig struct { @@ -195,7 +195,7 @@ func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI // 遍历更新监听证书 if len(listenerIds) == 0 { - return xerrors.New("listener not found") + return errors.New("listener not found") } else { var errs []error @@ -445,7 +445,7 @@ func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Up } } - uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.AliyunCASUploaderConfig{ AccessKeyId: accessKeyId, AccessKeySecret: accessKeySecret, Region: casRegion, diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go index 1fe99458..4bd91611 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb" ) type AliyunCLBDeployerConfig struct { @@ -63,7 +63,7 @@ func NewWithLogger(config *AliyunCLBDeployerConfig, logger logger.Logger) (*Aliy return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := providerSlb.New(&providerSlb.AliyunSLBUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.AliyunSLBUploaderConfig{ AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, Region: config.Region, @@ -161,7 +161,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI // 遍历更新监听证书 if len(listenerPorts) == 0 { - return xerrors.New("listener not found") + return errors.New("listener not found") } else { var errs []error diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go index 35286919..6deee907 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -14,7 +14,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" ) type AliyunNLBDeployerConfig struct { @@ -153,7 +153,7 @@ func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertI // 遍历更新监听证书 if len(listenerIds) == 0 { - return xerrors.New("listener not found") + return errors.New("listener not found") } else { var errs []error @@ -248,7 +248,7 @@ func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Up } } - uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.AliyunCASUploaderConfig{ AccessKeyId: accessKeyId, AccessKeySecret: accessKeySecret, Region: casRegion, diff --git a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go index 58289fc2..75c634b8 100644 --- a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go +++ b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go @@ -14,7 +14,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" ) type AliyunWAFDeployerConfig struct { @@ -141,7 +141,7 @@ func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Up } } - uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.AliyunCASUploaderConfig{ AccessKeyId: accessKeyId, AccessKeySecret: accessKeySecret, Region: casRegion, diff --git a/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go new file mode 100644 index 00000000..086dd415 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go @@ -0,0 +1,133 @@ +package awscloudfront + +import ( + "context" + "errors" + + aws "github.com/aws/aws-sdk-go-v2/aws" + awsCfg "github.com/aws/aws-sdk-go-v2/config" + awsCred "github.com/aws/aws-sdk-go-v2/credentials" + awsCf "github.com/aws/aws-sdk-go-v2/service/cloudfront" + awsCfTypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types" + 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/aws-acm" +) + +type AWSCloudFrontDeployerConfig struct { + // AWS AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // AWS SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // AWS 区域。 + Region string `json:"region"` + // AWS CloudFront 分配 ID。 + DistributionId string `json:"distributionId"` +} + +type AWSCloudFrontDeployer struct { + config *AWSCloudFrontDeployerConfig + logger logger.Logger + sdkClient *awsCf.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AWSCloudFrontDeployer)(nil) + +func New(config *AWSCloudFrontDeployerConfig) (*AWSCloudFrontDeployer, error) { + return NewWithLogger(config, logger.NewNilLogger()) +} + +func NewWithLogger(config *AWSCloudFrontDeployerConfig, logger logger.Logger) (*AWSCloudFrontDeployer, 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.SecretAccessKey, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := uploaderp.New(&uploaderp.AWSCertificateManagerUploaderConfig{ + AccessKeyId: config.AccessKeyId, + SecretAccessKey: config.SecretAccessKey, + Region: config.Region, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &AWSCloudFrontDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AWSCloudFrontDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.DistributionId == "" { + return nil, errors.New("config `distribuitionId` is required") + } + + // 上传证书到 ACM + 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://docs.aws.amazon.com/en_us/cloudfront/latest/APIReference/API_GetDistributionConfig.html + getDistributionConfigReq := &awsCf.GetDistributionConfigInput{ + Id: aws.String(d.config.DistributionId), + } + getDistributionConfigResp, err := d.sdkClient.GetDistributionConfig(context.TODO(), getDistributionConfigReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cloudfront.GetDistributionConfig'") + } + + d.logger.Logt("已获取分配配置", getDistributionConfigResp) + + // 更新分配配置 + // REF: https://docs.aws.amazon.com/zh_cn/cloudfront/latest/APIReference/API_UpdateDistribution.html + updateDistributionReq := &awsCf.UpdateDistributionInput{ + Id: aws.String(d.config.DistributionId), + DistributionConfig: getDistributionConfigResp.DistributionConfig, + IfMatch: getDistributionConfigResp.ETag, + } + if updateDistributionReq.DistributionConfig.ViewerCertificate == nil { + updateDistributionReq.DistributionConfig.ViewerCertificate = &awsCfTypes.ViewerCertificate{} + } + updateDistributionReq.DistributionConfig.ViewerCertificate.CloudFrontDefaultCertificate = aws.Bool(false) + updateDistributionReq.DistributionConfig.ViewerCertificate.ACMCertificateArn = aws.String(upres.CertId) + updateDistributionResp, err := d.sdkClient.UpdateDistribution(context.TODO(), updateDistributionReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cloudfront.UpdateDistribution'") + } + + d.logger.Logt("已更新分配配置", updateDistributionResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, secretAccessKey, region string) (*awsCf.Client, error) { + cfg, err := awsCfg.LoadDefaultConfig(context.TODO()) + if err != nil { + return nil, err + } + + client := awsCf.NewFromConfig(cfg, func(o *awsCf.Options) { + o.Region = region + o.Credentials = aws.NewCredentialsCache(awsCred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, "")) + }) + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront_test.go b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront_test.go new file mode 100644 index 00000000..89997b37 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront_test.go @@ -0,0 +1,80 @@ +package awscloudfront_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-cloudfront" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fSecretAccessKey string + fRegion string + fDistribuitionId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_AWSCLOUDFRONT_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fDistribuitionId, argsPrefix+"DISTRIBUTIONID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./aws_cloudfront_test.go -args \ + --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_SECRETACCESSKEY="your-secret-access-id" \ + --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_REGION="us-east-1" \ + --CERTIMATE_DEPLOYER_AWSCLOUDFRONT_DISTRIBUTIONID="your-distribution-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("SECRETACCESSKEY: %v", fSecretAccessKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("DISTRIBUTIONID: %v", fDistribuitionId), + }, "\n")) + + deployer, err := provider.New(&provider.AWSCloudFrontDeployerConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + Region: fRegion, + DistribuitionId: fDistribuitionId, + }) + 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/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go index 9497bb6a..2a695513 100644 --- a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go +++ b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -12,7 +12,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerCdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/byteplus-cdn" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/byteplus-cdn" ) type BytePlusCDNDeployerConfig struct { @@ -50,7 +50,7 @@ func NewWithLogger(config *BytePlusCDNDeployerConfig, logger logger.Logger) (*By client.Client.SetAccessKey(config.AccessKey) client.Client.SetSecretKey(config.SecretKey) - uploader, err := providerCdn.New(&providerCdn.ByteplusCDNUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.ByteplusCDNUploaderConfig{ AccessKey: config.AccessKey, SecretKey: config.SecretKey, }) @@ -103,7 +103,7 @@ func (d *BytePlusCDNDeployer) Deploy(ctx context.Context, certPem string, privke if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 { // 所有可关联的域名都配置了该证书,跳过部署 } else { - return nil, xerrors.New("domain not found") + return nil, errors.New("domain not found") } } } else { diff --git a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go index 4d9f4ac4..283d9907 100644 --- a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go +++ b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go @@ -10,7 +10,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerDoge "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/dogecloud" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/dogecloud" dogesdk "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk" ) @@ -47,7 +47,7 @@ func NewWithLogger(config *DogeCloudCDNDeployerConfig, logger logger.Logger) (*D client := dogesdk.NewClient(config.AccessKey, config.SecretKey) - uploader, err := providerDoge.New(&providerDoge.DogeCloudUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.DogeCloudUploaderConfig{ AccessKey: config.AccessKey, SecretKey: config.SecretKey, }) diff --git a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index 5c303ce4..31782af3 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerScm "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm" hwsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk" ) @@ -59,7 +59,7 @@ func NewWithLogger(config *HuaweiCloudCDNDeployerConfig, logger logger.Logger) ( return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := providerScm.New(&providerScm.HuaweiCloudSCMUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.HuaweiCloudSCMUploaderConfig{ AccessKeyId: config.AccessKeyId, SecretAccessKey: config.SecretAccessKey, }) diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go index 5a12e101..3fa17a70 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -19,7 +19,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerElb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-elb" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-elb" hwsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-sdk" ) @@ -70,7 +70,7 @@ func NewWithLogger(config *HuaweiCloudELBDeployerConfig, logger logger.Logger) ( return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := providerElb.New(&providerElb.HuaweiCloudELBUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.HuaweiCloudELBUploaderConfig{ AccessKeyId: config.AccessKeyId, SecretAccessKey: config.SecretAccessKey, Region: config.Region, @@ -205,7 +205,7 @@ func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context, certP // 遍历更新监听器证书 if len(listenerIds) == 0 { - return xerrors.New("listener not found") + return errors.New("listener not found") } else { var errs []error diff --git a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go index 03ae1762..6591da1b 100644 --- a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go +++ b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go @@ -11,7 +11,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerQiniu "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert" qiniusdk "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk" ) @@ -48,7 +48,7 @@ func NewWithLogger(config *QiniuCDNDeployerConfig, logger logger.Logger) (*Qiniu client := qiniusdk.NewClient(auth.New(config.AccessKey, config.SecretKey)) - uploader, err := providerQiniu.New(&providerQiniu.QiniuSSLCertUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.QiniuSSLCertUploaderConfig{ AccessKey: config.AccessKey, SecretKey: config.SecretKey, }) diff --git a/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go index 9ae267ba..4530071c 100644 --- a/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go +++ b/internal/pkg/core/deployer/providers/qiniu-pili/qiniu_pili.go @@ -10,7 +10,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerQiniu "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert" ) type QiniuPiliDeployerConfig struct { @@ -48,7 +48,7 @@ func NewWithLogger(config *QiniuPiliDeployerConfig, logger logger.Logger) (*Qini manager := pili.NewManager(pili.ManagerConfig{AccessKey: config.AccessKey, SecretKey: config.SecretKey}) - uploader, err := providerQiniu.New(&providerQiniu.QiniuSSLCertUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.QiniuSSLCertUploaderConfig{ AccessKey: config.AccessKey, SecretKey: config.SecretKey, }) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go index a4a05a1b..61418845 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -15,7 +15,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" ) type TencentCloudCDNDeployerConfig struct { @@ -59,7 +59,7 @@ func NewWithLogger(config *TencentCloudCDNDeployerConfig, logger logger.Logger) return nil, xerrors.Wrap(err, "failed to create sdk clients") } - uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{ SecretId: config.SecretId, SecretKey: config.SecretKey, }) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go index 17958eac..d67eb383 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -14,7 +14,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" ) type TencentCloudCLBDeployerConfig struct { @@ -69,7 +69,7 @@ func NewWithLogger(config *TencentCloudCLBDeployerConfig, logger logger.Logger) return nil, xerrors.Wrap(err, "failed to create sdk clients") } - uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{ SecretId: config.SecretId, SecretKey: config.SecretKey, }) @@ -183,7 +183,7 @@ func (d *TencentCloudCLBDeployer) deployToLoadbalancer(ctx context.Context, clou // 遍历更新监听器证书 if len(listenerIds) == 0 { - return xerrors.New("listener not found") + return errors.New("listener not found") } else { var errs []error diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go index 23c2d11a..c2af84fb 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" ) type TencentCloudCOSDeployerConfig struct { @@ -56,7 +56,7 @@ func NewWithLogger(config *TencentCloudCOSDeployerConfig, logger logger.Logger) return nil, xerrors.Wrap(err, "failed to create sdk clients") } - uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{ SecretId: config.SecretId, SecretKey: config.SecretKey, }) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-css/tencentcloud_css.go b/internal/pkg/core/deployer/providers/tencentcloud-css/tencentcloud_css.go index 391fc05b..31057017 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-css/tencentcloud_css.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-css/tencentcloud_css.go @@ -12,7 +12,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" ) type TencentCloudCSSDeployerConfig struct { @@ -51,7 +51,7 @@ func NewWithLogger(config *TencentCloudCSSDeployerConfig, logger logger.Logger) return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{ SecretId: config.SecretId, SecretKey: config.SecretKey, }) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go index 29f6e33c..4cddbc59 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -14,7 +14,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" ) type TencentCloudECDNDeployerConfig struct { @@ -58,7 +58,7 @@ func NewWithLogger(config *TencentCloudECDNDeployerConfig, logger logger.Logger) return nil, xerrors.Wrap(err, "failed to create sdk clients") } - uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{ SecretId: config.SecretId, SecretKey: config.SecretKey, }) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go b/internal/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go index fb45ea9e..2de4dcc5 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-eo/tencentcloud_eo.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" ) type TencentCloudEODeployerConfig struct { @@ -59,7 +59,7 @@ func NewWithLogger(config *TencentCloudEODeployerConfig, logger logger.Logger) ( return nil, xerrors.Wrap(err, "failed to create sdk clients") } - uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{ SecretId: config.SecretId, SecretKey: config.SecretKey, }) @@ -77,7 +77,7 @@ func NewWithLogger(config *TencentCloudEODeployerConfig, logger logger.Logger) ( func (d *TencentCloudEODeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { if d.config.ZoneId == "" { - return nil, xerrors.New("config `zoneId` is required") + return nil, errors.New("config `zoneId` is required") } // 上传证书到 SSL diff --git a/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go index ec127f41..fa104ba3 100644 --- a/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go +++ b/internal/pkg/core/deployer/providers/ucloud-ucdn/ucloud_ucdn.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - uploaderSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl" ) type UCloudUCDNDeployerConfig struct { @@ -54,7 +54,7 @@ func NewWithLogger(config *UCloudUCDNDeployerConfig, logger logger.Logger) (*UCl return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := uploaderSsl.New(&uploaderSsl.UCloudUSSLUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.UCloudUSSLUploaderConfig{ PrivateKey: config.PrivateKey, PublicKey: config.PublicKey, ProjectId: config.ProjectId, diff --git a/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go b/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go index b55d1acc..ccf03224 100644 --- a/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go +++ b/internal/pkg/core/deployer/providers/ucloud-us3/ucloud_us3.go @@ -11,7 +11,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - uploaderSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/ucloud-ussl" usdkFile "github.com/usual2970/certimate/internal/pkg/vendors/ucloud-sdk/ufile" ) @@ -57,7 +57,7 @@ func NewWithLogger(config *UCloudUS3DeployerConfig, logger logger.Logger) (*UClo return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := uploaderSsl.New(&uploaderSsl.UCloudUSSLUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.UCloudUSSLUploaderConfig{ PrivateKey: config.PrivateKey, PublicKey: config.PublicKey, ProjectId: config.ProjectId, diff --git a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go index 02adaf1b..de3e76e4 100644 --- a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -12,7 +12,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - uploaderCdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-cdn" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-cdn" ) type VolcEngineCDNDeployerConfig struct { @@ -50,7 +50,7 @@ func NewWithLogger(config *VolcEngineCDNDeployerConfig, logger logger.Logger) (* client.Client.SetAccessKey(config.AccessKeyId) client.Client.SetSecretKey(config.AccessKeySecret) - uploader, err := uploaderCdn.New(&uploaderCdn.VolcEngineCDNUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.VolcEngineCDNUploaderConfig{ AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, }) @@ -103,7 +103,7 @@ func (d *VolcEngineCDNDeployer) Deploy(ctx context.Context, certPem string, priv if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 { // 所有可关联的域名都配置了该证书,跳过部署 } else { - return nil, xerrors.New("domain not found") + return nil, errors.New("domain not found") } } } else { diff --git a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go index b4acbfa0..08c096fd 100644 --- a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go +++ b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - uploaderCertCenter "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter" ) type VolcEngineCLBDeployerConfig struct { @@ -57,7 +57,7 @@ func NewWithLogger(config *VolcEngineCLBDeployerConfig, logger logger.Logger) (* return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := uploaderCertCenter.New(&uploaderCertCenter.VolcEngineCertCenterUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.VolcEngineCertCenterUploaderConfig{ AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, Region: config.Region, diff --git a/internal/pkg/core/deployer/providers/volcengine-dcdn/volcengine_dcdn.go b/internal/pkg/core/deployer/providers/volcengine-dcdn/volcengine_dcdn.go index 86c8776d..0f9f8e51 100644 --- a/internal/pkg/core/deployer/providers/volcengine-dcdn/volcengine_dcdn.go +++ b/internal/pkg/core/deployer/providers/volcengine-dcdn/volcengine_dcdn.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - uploaderCertCenter "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter" ) type VolcEngineDCDNDeployerConfig struct { @@ -54,7 +54,7 @@ func NewWithLogger(config *VolcEngineDCDNDeployerConfig, logger logger.Logger) ( return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := uploaderCertCenter.New(&uploaderCertCenter.VolcEngineCertCenterUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.VolcEngineCertCenterUploaderConfig{ AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, Region: config.Region, diff --git a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go index 083837b7..b23f4c6c 100644 --- a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go +++ b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go @@ -13,7 +13,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - uploaderLive "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-live" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-live" ) type VolcEngineLiveDeployerConfig struct { @@ -51,7 +51,7 @@ func NewWithLogger(config *VolcEngineLiveDeployerConfig, logger logger.Logger) ( client.SetAccessKey(config.AccessKeyId) client.SetSecretKey(config.AccessKeySecret) - uploader, err := uploaderLive.New(&uploaderLive.VolcEngineLiveUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.VolcEngineLiveUploaderConfig{ AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, }) diff --git a/internal/pkg/core/deployer/providers/volcengine-tos/volcengine_tos.go b/internal/pkg/core/deployer/providers/volcengine-tos/volcengine_tos.go index 0c5501ec..9a238f27 100644 --- a/internal/pkg/core/deployer/providers/volcengine-tos/volcengine_tos.go +++ b/internal/pkg/core/deployer/providers/volcengine-tos/volcengine_tos.go @@ -11,7 +11,7 @@ import ( "github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/core/uploader" - uploaderCertCenter "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-certcenter" ) type VolcEngineTOSDeployerConfig struct { @@ -54,7 +54,7 @@ func NewWithLogger(config *VolcEngineTOSDeployerConfig, logger logger.Logger) (* return nil, xerrors.Wrap(err, "failed to create sdk client") } - uploader, err := uploaderCertCenter.New(&uploaderCertCenter.VolcEngineCertCenterUploaderConfig{ + uploader, err := uploaderp.New(&uploaderp.VolcEngineCertCenterUploaderConfig{ AccessKeyId: config.AccessKeyId, AccessKeySecret: config.AccessKeySecret, Region: config.Region, diff --git a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go index e79ab6e7..cac4d833 100644 --- a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go +++ b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go @@ -92,6 +92,5 @@ func createSdkClient(accessKeyId, secretAccessKey, region string) (*awsAcm.Clien o.Region = region o.Credentials = aws.NewCredentialsCache(awsCred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, "")) }) - return client, nil } diff --git a/internal/pkg/utils/certs/converter.go b/internal/pkg/utils/certs/converter.go index eb13a358..74a9a5ab 100644 --- a/internal/pkg/utils/certs/converter.go +++ b/internal/pkg/utils/certs/converter.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "crypto/x509" "encoding/pem" + "errors" xerrors "github.com/pkg/errors" ) @@ -18,7 +19,7 @@ import ( // - err: 错误。 func ConvertCertificateToPEM(cert *x509.Certificate) (certPem string, err error) { if cert == nil { - return "", xerrors.New("cert is nil") + return "", errors.New("`cert` is nil") } block := &pem.Block{ @@ -39,7 +40,7 @@ func ConvertCertificateToPEM(cert *x509.Certificate) (certPem string, err error) // - err: 错误。 func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (privkeyPem string, err error) { if privkey == nil { - return "", xerrors.New("privkey is nil") + return "", errors.New("`privkey` is nil") } data, err := x509.MarshalECPrivateKey(privkey) diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 3d85e6de..c9b70570 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -23,6 +23,7 @@ import DeployNodeConfigFormAliyunLiveConfig from "./DeployNodeConfigFormAliyunLi import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLBConfig"; import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig"; import DeployNodeConfigFormAliyunWAFConfig from "./DeployNodeConfigFormAliyunWAFConfig"; +import DeployNodeConfigFormAWSCloudFrontConfig from "./DeployNodeConfigFormAWSCloudFrontConfig"; import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaiduCloudCDNConfig"; import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig"; import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig"; @@ -136,6 +137,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.ALIYUN_WAF: return ; + case DEPLOY_PROVIDERS.AWS_CLOUDFRONT: + return ; case DEPLOY_PROVIDERS.BAIDUCLOUD_CDN: return ; case DEPLOY_PROVIDERS.BYTEPLUS_CDN: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx new file mode 100644 index 00000000..f1689ced --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx @@ -0,0 +1,79 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormAWSCloudFrontConfigFieldValues = Nullish<{ + region: string; + distributionId: string; +}>; + +export type DeployNodeConfigFormAWSCloudFrontConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAWSCloudFrontConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAWSCloudFrontConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormAWSCloudFrontConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAWSCloudFrontConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormAWSCloudFrontConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.deploy.form.aws_cloudfront_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aws_cloudfront_region.placeholder")) + .trim(), + distributionId: z + .string({ message: t("workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default DeployNodeConfigFormAWSCloudFrontConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx index 9794f9fd..b7206d04 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunWAFConfig.tsx @@ -35,8 +35,8 @@ const DeployNodeConfigFormAliyunWAFConfig = ({ .nonempty(t("workflow_node.deploy.form.aliyun_waf_region.placeholder")) .trim(), instanceId: z - .string({ message: t("workflow_node.deploy.form.aliyun_instance_id.placeholder") }) - .nonempty(t("workflow_node.deploy.form.aliyun_instance_id.placeholder")) + .string({ message: t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder")) .max(64, t("common.errmsg.string_max", { max: 64 })) .trim(), }); diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 9a05d180..16fe5da6 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -63,13 +63,13 @@ export const accessProvidersMap: Map [ type, diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index dbe271e7..4fb7e387 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -47,6 +47,7 @@ "common.provider.aliyun.oss": "Alibaba Cloud - OSS (Object Storage Service)", "common.provider.aliyun.waf": "Alibaba Cloud - WAF (Web Application Firewall)", "common.provider.aws": "AWS", + "common.provider.aws.cloudfront": "AWS - CloudFront", "common.provider.aws.route53": "AWS - Route53", "common.provider.azure": "Azure", "common.provider.azure.dns": "Azure - DNS", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index bafac2e2..7ba7e643 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -37,8 +37,8 @@ "workflow_node.apply.form.provider_access.placeholder": "Please select an authorization of DNS provider", "workflow_node.apply.form.provider_access.tooltip": "Used to manage DNS records during ACME DNS-01 authentication.", "workflow_node.apply.form.provider_access.button": "Create", - "workflow_node.apply.form.aws_route53_region.label": "AWS Route53 Region", - "workflow_node.apply.form.aws_route53_region.placeholder": "Please enter AWS Route53 region (e.g. us-east-1)", + "workflow_node.apply.form.aws_route53_region.label": "AWS Region", + "workflow_node.apply.form.aws_route53_region.placeholder": "Please enter AWS region (e.g. us-east-1)", "workflow_node.apply.form.aws_route53_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", "workflow_node.apply.form.aws_route53_hosted_zone_id.label": "AWS Route53 hosted zone ID", "workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder": "Please enter AWS Route53 hosted zone ID", @@ -159,6 +159,12 @@ "workflow_node.deploy.form.aliyun_waf_instance_id.label": "Alibaba Cloud WAF instance ID", "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "Please enter Alibaba Cloud WAF instance ID", "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "For more information, see https://waf.console.aliyun.com", + "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS Region", + "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "Please enter AWS region (e.g. us-east-1)", + "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", + "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront distribution ID", + "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "Please enter AWS CloudFront distribution ID", + "workflow_node.deploy.form.aws_cloudfront_distribution_id.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html", "workflow_node.deploy.form.baiducloud_cdn_domain.label": "Baidu Cloud CDN domain", "workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "Please enter Baidu Cloud CDN domain name", "workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "For more information, see https://console.bce.baidu.com/cdn", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 45072e74..3035e533 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -47,6 +47,7 @@ "common.provider.aliyun.oss": "阿里云 - 对象存储 OSS", "common.provider.aliyun.waf": "阿里云 - Web 应用防火墙 WAF", "common.provider.aws": "AWS", + "common.provider.aws.cloudfront": "AWS - CloudFront", "common.provider.aws.route53": "AWS - Route53", "common.provider.azure": "Azure", "common.provider.azure.dns": "Azure - DNS", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 2d43d042..147bb63a 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -38,8 +38,8 @@ "workflow_node.apply.form.provider_access.tooltip": "用于 ACME DNS-01 认证时操作域名解析记录,注意与部署阶段所需的主机提供商相区分。", "workflow_node.apply.form.provider_access.button": "新建", "workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:由于表单限制,你同样需要为本地部署选择一个授权 —— 即使它是空白的。", - "workflow_node.apply.form.aws_route53_region.label": "AWS Route53 区域", - "workflow_node.apply.form.aws_route53_region.placeholder": "请输入 AWS Route53 区域(例如:us-east-1)", + "workflow_node.apply.form.aws_route53_region.label": "AWS 区域", + "workflow_node.apply.form.aws_route53_region.placeholder": "请输入 AWS 区域(例如:us-east-1)", "workflow_node.apply.form.aws_route53_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", "workflow_node.apply.form.aws_route53_hosted_zone_id.label": "AWS Route53 托管区域 ID", "workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder": "请输入 AWS Route53 托管区域 ID", @@ -159,6 +159,12 @@ "workflow_node.deploy.form.aliyun_waf_instance_id.label": "阿里云 WAF 实例 ID", "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "请输入阿里云 WAF 实例 ID", "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 https://waf.console.aliyun.com", + "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS 区域", + "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "请输入 AWS 区域(例如:us-east-1)", + "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", + "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront 分配 ID", + "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "请输入 AWS CloudFront 分配 ID", + "workflow_node.deploy.form.aws_cloudfront_distribution_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html", "workflow_node.deploy.form.baiducloud_cdn_domain.label": "百度智能云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "请输入百度智能云 CDN 加速域名", "workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/cdn

泛域名表示形式为:*.example.com", From 9f7cffce21a69b8d43371eb81541cba5abe4dc3b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 23:47:37 +0800 Subject: [PATCH 08/11] feat: allow fallback to use scp on deployment to ssh --- README.md | 2 +- README_EN.md | 2 +- go.mod | 1 + go.sum | 3 ++ internal/deployer/providers.go | 1 + .../pkg/core/deployer/providers/ssh/ssh.go | 53 ++++++++++++++++--- .../node/DeployNodeConfigFormSSHConfig.tsx | 13 ++++- .../i18n/locales/en/nls.workflow.nodes.json | 2 + .../i18n/locales/zh/nls.workflow.nodes.json | 2 + 9 files changed, 69 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 28438c76..97e851ea 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ make local.run | 提供商 | 备注 | | :-------------------------------------- | :------------------------------------------------------------------ | | 本地部署 | 可部署到本地服务器 | -| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP) | +| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP/SCP) | | Webhook 回调 | 可部署到 Webhook | | [Kubernetes](https://kubernetes.io/) | 可部署到 Kubernetes Secret | | [阿里云](https://www.aliyun.com/) | 可部署到阿里云 OSS、CDN、DCDN、SLB(CLB/ALB/NLB)、WAF、Live 等服务 | diff --git a/README_EN.md b/README_EN.md index 5b3e67ff..b5c71b3d 100644 --- a/README_EN.md +++ b/README_EN.md @@ -117,7 +117,7 @@ The following hosting providers are supported: | Provider | Remarks | | :---------------------------------------------- | :------------------------------------------------------------------------------- | | Local | Supports deployment to local servers | -| SSH | Supports deployment to remote servers (via SSH+SFTP) | +| SSH | Supports deployment to remote servers (via SSH+SFTP/SCP) | | Webhook | Supports deployment to Webhook | | [Kubernetes](https://kubernetes.io/) | Supports deployment to Kubernetes Secret | | [Alibaba Cloud](https://www.alibabacloud.com/) | Supports deployment to Alibaba Cloud OSS, CDN, DCDN, SLB(CLB/ALB/NLB), WAF, Live | diff --git a/go.mod b/go.mod index 3bb9f1d9..6cd082bf 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/pkg/sftp v1.13.7 github.com/pocketbase/dbx v1.11.0 github.com/pocketbase/pocketbase v0.24.4 + github.com/povsister/scp v0.0.0-20240802064259-28781e87b246 github.com/qiniu/go-sdk/v7 v7.25.2 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1084 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1084 diff --git a/go.sum b/go.sum index 2111474e..7dde5dc8 100644 --- a/go.sum +++ b/go.sum @@ -743,6 +743,8 @@ github.com/pocketbase/pocketbase v0.24.4 h1:kw/c23HccoxMV/19U9QlDcvNJgQ66vlUrxGQ github.com/pocketbase/pocketbase v0.24.4/go.mod h1:EfXV/8RUY76jA6g1RPNHjOuW7wTd2bz0QlvAI/RU8YY= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/povsister/scp v0.0.0-20240802064259-28781e87b246 h1:c4D8BPWLOxxdaxQLfLKQXH2YXY/E9yo3jrDSL54XrTw= +github.com/povsister/scp v0.0.0-20240802064259-28781e87b246/go.mod h1:i1Au86ZXK0ZalQNyBp2njCcyhSCR/QP/AMfILip+zNI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= @@ -933,6 +935,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 28e4108c..344c78e6 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -350,6 +350,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, SshPassword: access.Password, SshKey: access.Key, SshKeyPassphrase: access.KeyPassphrase, + UseSCP: maps.GetValueAsBool(options.ProviderDeployConfig, "useSCP"), PreCommand: maps.GetValueAsString(options.ProviderDeployConfig, "preCommand"), PostCommand: maps.GetValueAsString(options.ProviderDeployConfig, "postCommand"), OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(providerSSH.OUTPUT_FORMAT_PEM))), diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index 4fffce74..20ea348a 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -10,6 +10,7 @@ import ( xerrors "github.com/pkg/errors" "github.com/pkg/sftp" + "github.com/povsister/scp" "golang.org/x/crypto/ssh" "github.com/usual2970/certimate/internal/pkg/core/deployer" @@ -32,6 +33,8 @@ type SshDeployerConfig struct { SshKey string `json:"sshKey,omitempty"` // SSH 登录私钥口令。 SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"` + // 是否回退使用 SCP。 + UseSCP bool `json:"useSCP,omitempty"` // 前置命令。 PreCommand string `json:"preCommand,omitempty"` // 后置命令。 @@ -112,13 +115,13 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str // 上传证书和私钥文件 switch d.config.OutputFormat { case OUTPUT_FORMAT_PEM: - if err := writeSftpFileString(client, d.config.OutputCertPath, certPem); err != nil { + if err := writeFileString(client, d.config.UseSCP, d.config.OutputCertPath, certPem); err != nil { return nil, xerrors.Wrap(err, "failed to upload certificate file") } d.logger.Logt("certificate file uploaded") - if err := writeSftpFileString(client, d.config.OutputKeyPath, privkeyPem); err != nil { + if err := writeFileString(client, d.config.UseSCP, d.config.OutputKeyPath, privkeyPem); err != nil { return nil, xerrors.Wrap(err, "failed to upload private key file") } @@ -132,7 +135,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str d.logger.Logt("certificate transformed to PFX") - if err := writeSftpFile(client, d.config.OutputCertPath, pfxData); err != nil { + if err := writeFile(client, d.config.UseSCP, d.config.OutputCertPath, pfxData); err != nil { return nil, xerrors.Wrap(err, "failed to upload certificate file") } @@ -146,7 +149,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str d.logger.Logt("certificate transformed to JKS") - if err := writeSftpFile(client, d.config.OutputCertPath, jksData); err != nil { + if err := writeFile(client, d.config.UseSCP, d.config.OutputCertPath, jksData); err != nil { return nil, xerrors.Wrap(err, "failed to upload certificate file") } @@ -223,11 +226,47 @@ func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) return stdoutBuf.String(), stderrBuf.String(), nil } -func writeSftpFileString(sshCli *ssh.Client, path string, content string) error { - return writeSftpFile(sshCli, path, []byte(content)) +func writeFileString(sshCli *ssh.Client, useSCP bool, path string, content string) error { + if useSCP { + return writeFileStringWithSCP(sshCli, path, content) + } + + return writeFileStringWithSFTP(sshCli, path, content) } -func writeSftpFile(sshCli *ssh.Client, path string, data []byte) error { +func writeFile(sshCli *ssh.Client, useSCP bool, path string, data []byte) error { + if useSCP { + return writeFileWithSCP(sshCli, path, data) + } + + return writeFileWithSFTP(sshCli, path, data) +} + +func writeFileStringWithSCP(sshCli *ssh.Client, path string, content string) error { + return writeFileWithSCP(sshCli, path, []byte(content)) +} + +func writeFileWithSCP(sshCli *ssh.Client, path string, data []byte) error { + scpCli, err := scp.NewClientFromExistingSSH(sshCli, &scp.ClientOption{}) + if err != nil { + return xerrors.Wrap(err, "failed to create scp client") + } + defer scpCli.Close() + + reader := bytes.NewReader(data) + err = scpCli.CopyToRemote(reader, path, &scp.FileTransferOption{}) + if err != nil { + return xerrors.Wrap(err, "failed to write to remote file") + } + + return nil +} + +func writeFileStringWithSFTP(sshCli *ssh.Client, path string, content string) error { + return writeFileWithSFTP(sshCli, path, []byte(content)) +} + +func writeFileWithSFTP(sshCli *ssh.Client, path string, data []byte) error { sftpCli, err := sftp.NewClient(sshCli) if err != nil { return xerrors.Wrap(err, "failed to create sftp client") diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx index 976ca079..1e176d7b 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx @@ -1,6 +1,6 @@ import { useTranslation } from "react-i18next"; import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons"; -import { Button, Dropdown, Form, type FormInstance, Input, Select } from "antd"; +import { Button, Dropdown, Form, type FormInstance, Input, Select, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -17,6 +17,7 @@ type DeployNodeConfigFormSSHConfigFieldValues = Nullish<{ jksStorepass?: string | null; preCommand?: string | null; postCommand?: string | null; + useSCP?: boolean; }>; export type DeployNodeConfigFormSSHConfigProps = { @@ -89,6 +90,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini .string() .max(20480, t("common.errmsg.string_max", { max: 20480 })) .nullish(), + useSCP: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -261,6 +263,15 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini + + } + > + + ); }; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 7ba7e643..964633e0 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -291,6 +291,8 @@ "workflow_node.deploy.form.ssh_post_command.placeholder": "Please enter command to be executed after uploading files", "workflow_node.deploy.form.ssh_preset_scripts.button": "Use preset scripts", "workflow_node.deploy.form.ssh_preset_scripts.option.reload_nginx.label": "POSIX Bash - Reload nginx", + "workflow_node.deploy.form.ssh_use_scp.label": "Fallback to use SCP", + "workflow_node.deploy.form.ssh_use_scp.tooltip": "If the remote server does not support SFTP, please enable this option to fallback to SCP.", "workflow_node.deploy.form.tencentcloud_cdn_domain.label": "Tencent Cloud CDN domain", "workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "Please enter Tencent Cloud CDN domain name", "workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "For more information, see https://console.tencentcloud.com/cdn", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 147bb63a..d132f935 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -291,6 +291,8 @@ "workflow_node.deploy.form.ssh_post_command.placeholder": "请输入保存文件后执行的命令", "workflow_node.deploy.form.ssh_preset_scripts.button": "使用预设脚本", "workflow_node.deploy.form.ssh_preset_scripts.option.reload_nginx.label": "POSIX Bash - 重启 nginx 进程", + "workflow_node.deploy.form.ssh_use_scp.label": "回退使用 SCP", + "workflow_node.deploy.form.ssh_use_scp.tooltip": "如果你的远程服务器不支持 SFTP,请开启此选项回退为 SCP。", "workflow_node.deploy.form.tencentcloud_cdn_domain.label": "腾讯云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "请输入腾讯云 CDN 加速域名", "workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/cdn

泛域名表示形式为:*.example.com", From a78a815cccab24e188330a6081b3e6bf620cc659 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 23:54:45 +0800 Subject: [PATCH 09/11] fix: typo --- ui/src/i18n/locales/zh/nls.access.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 7d51ad43..6fce9673 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -175,7 +175,7 @@ "access.form.westcn_username.label": "西部数码用户名", "access.form.westcn_username.placeholder": "请输入西部数码用户名", "access.form.westcn_username.tooltip": "这是什么?请参阅 https://www.west.cn/CustomerCenter/doc/apiv2.html", - "access.form.westcn_api_password.label": "西部数据 API 密码", - "access.form.westcn_api_password.placeholder": "请输入西部数据 API 密码", + "access.form.westcn_api_password.label": "西部数码 API 密码", + "access.form.westcn_api_password.placeholder": "请输入西部数码 API 密码", "access.form.westcn_api_password.tooltip": "这是什么?请参阅 https://www.west.cn/CustomerCenter/doc/apiv2.html" } From 469d4b35c195c436b157dbf5ac14b803df6640fe Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 24 Jan 2025 01:38:06 +0800 Subject: [PATCH 10/11] feat: implement gname api sdk --- .../acme-dns-01/lego-providers/gname/gname.go | 39 ----- internal/pkg/vendors/gname-sdk/api.go | 102 +++++++++++ internal/pkg/vendors/gname-sdk/client.go | 162 ++++++++++++++++++ 3 files changed, 264 insertions(+), 39 deletions(-) delete mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go create mode 100644 internal/pkg/vendors/gname-sdk/api.go create mode 100644 internal/pkg/vendors/gname-sdk/client.go diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go deleted file mode 100644 index a4045997..00000000 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go +++ /dev/null @@ -1,39 +0,0 @@ -package gname - -import ( - "errors" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/providers/dns/westcn" -) - -type GnameApplicantConfig struct { - Username string `json:"username"` - ApiPassword string `json:"apiPassword"` - DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` - DnsTTL int32 `json:"dnsTTL,omitempty"` -} - -func NewChallengeProvider(config *GnameApplicantConfig) (challenge.Provider, error) { - if config == nil { - return nil, errors.New("config is nil") - } - - providerConfig := westcn.NewDefaultConfig() - providerConfig.Username = config.Username - providerConfig.Password = config.ApiPassword - if config.DnsPropagationTimeout != 0 { - providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second - } - if config.DnsTTL != 0 { - providerConfig.TTL = int(config.DnsTTL) - } - - provider, err := westcn.NewDNSProviderConfig(providerConfig) - if err != nil { - return nil, err - } - - return provider, nil -} diff --git a/internal/pkg/vendors/gname-sdk/api.go b/internal/pkg/vendors/gname-sdk/api.go new file mode 100644 index 00000000..bb35f2e5 --- /dev/null +++ b/internal/pkg/vendors/gname-sdk/api.go @@ -0,0 +1,102 @@ +package gnamesdk + +type BaseResponse interface { + GetCode() int + GetMsg() string +} + +type AddDNSRecordRequest struct { + ZoneName string `json:"ym"` + RecordType string `json:"lx"` + RecordName string `json:"zj"` + RecordValue string `json:"jlz"` + MX int `json:"mx"` + TTL int `json:"ttl"` +} + +type AddDNSRecordResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data int `json:"data"` +} + +func (r *AddDNSRecordResponse) GetCode() int { + return r.Code +} + +func (r *AddDNSRecordResponse) GetMsg() string { + return r.Msg +} + +type EditDNSRecordRequest struct { + ID string `json:"jxid"` + ZoneName string `json:"ym"` + RecordType string `json:"lx"` + RecordName string `json:"zj"` + RecordValue string `json:"jlz"` + MX int `json:"mx"` + TTL int `json:"ttl"` +} + +type EditDNSRecordResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` +} + +func (r *EditDNSRecordResponse) GetCode() int { + return r.Code +} + +func (r *EditDNSRecordResponse) GetMsg() string { + return r.Msg +} + +type DeleteDNSRecordRequest struct { + ZoneName string `json:"ym"` + RecordId int `json:"jxid"` +} + +type DeleteDNSRecordResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` +} + +func (r *DeleteDNSRecordResponse) GetCode() int { + return r.Code +} + +func (r *DeleteDNSRecordResponse) GetMsg() string { + return r.Msg +} + +type ListDNSRecordRequest struct { + ZoneName string `json:"ym"` + Page *int `json:"page,omitempty"` + PageSize *int `json:"limit,omitempty"` +} + +type ListDNSRecordResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Count int `json:"count"` + Data []*DNSRecord `json:"data"` + Page int `json:"page"` + PageSize int `json:"pagesize"` +} + +type DNSRecord struct { + ID string `json:"id"` + ZoneName string `json:"ym"` + RecordType string `json:"lx"` + RecordName string `json:"zjt"` + RecordValue string `json:"jxz"` + MX int `json:"mx"` +} + +func (r *ListDNSRecordResponse) GetCode() int { + return r.Code +} + +func (r *ListDNSRecordResponse) GetMsg() string { + return r.Msg +} diff --git a/internal/pkg/vendors/gname-sdk/client.go b/internal/pkg/vendors/gname-sdk/client.go new file mode 100644 index 00000000..81812682 --- /dev/null +++ b/internal/pkg/vendors/gname-sdk/client.go @@ -0,0 +1,162 @@ +package gnamesdk + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "net/url" + "sort" + "strings" + "time" + + "github.com/go-resty/resty/v2" + + "github.com/usual2970/certimate/internal/pkg/utils/maps" +) + +type GnameClient struct { + appId string + appKey string + client *resty.Client +} + +func NewGnameClient(appId, appKey string) *GnameClient { + client := resty.New() + + return &GnameClient{ + appId: appId, + appKey: appKey, + client: client, + } +} + +func (c *GnameClient) WithTimeout(timeout time.Duration) *GnameClient { + c.client.SetTimeout(timeout) + return c +} + +func (c *GnameClient) AddDNSRecord(req *AddDNSRecordRequest) (*AddDNSRecordResponse, error) { + params := make(map[string]any) + jsonData, _ := json.Marshal(req) + json.Unmarshal(jsonData, ¶ms) + + result := AddDNSRecordResponse{} + err := c.sendRequestWithResult("/api/resolution/add", params, &result) + if err != nil { + return nil, err + } + return &result, nil +} + +func (c *GnameClient) EditDNSRecord(req *EditDNSRecordRequest) (*EditDNSRecordResponse, error) { + params := make(map[string]any) + jsonData, _ := json.Marshal(req) + json.Unmarshal(jsonData, ¶ms) + + result := EditDNSRecordResponse{} + err := c.sendRequestWithResult("/api/resolution/edit", params, &result) + if err != nil { + return nil, err + } + return &result, nil +} + +func (c *GnameClient) DeleteDNSRecord(req *DeleteDNSRecordRequest) (*DeleteDNSRecordResponse, error) { + params := make(map[string]any) + jsonData, _ := json.Marshal(req) + json.Unmarshal(jsonData, ¶ms) + + result := DeleteDNSRecordResponse{} + err := c.sendRequestWithResult("/api/resolution/delete", params, &result) + if err != nil { + return nil, err + } + return &result, nil +} + +func (c *GnameClient) ListDNSRecord(req *ListDNSRecordRequest) (*ListDNSRecordResponse, error) { + params := make(map[string]any) + jsonData, _ := json.Marshal(req) + json.Unmarshal(jsonData, ¶ms) + + result := ListDNSRecordResponse{} + err := c.sendRequestWithResult("/api/resolution/list", params, &result) + if err != nil { + return nil, err + } + return &result, nil +} + +func (c *GnameClient) generateSignature(params map[string]string) string { + // Step 1: Sort parameters by ASCII order + var keys []string + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + + // Step 2: Create string A with URL-encoded values + var pairs []string + for _, k := range keys { + encodedValue := url.QueryEscape(params[k]) + pairs = append(pairs, fmt.Sprintf("%s=%s", k, encodedValue)) + } + stringA := strings.Join(pairs, "&") + + // Step 3: Append appkey to create string B + stringB := stringA + c.appKey + + // Step 4: Calculate MD5 and convert to uppercase + hash := md5.Sum([]byte(stringB)) + return strings.ToUpper(fmt.Sprintf("%x", hash)) +} + +func (c *GnameClient) sendRequest(path string, params map[string]any) (*resty.Response, error) { + if params == nil { + params = make(map[string]any) + } + + data := make(map[string]string) + for k, v := range params { + data[k] = fmt.Sprintf("%v", v) + } + data["appid"] = c.appId + data["gntime"] = fmt.Sprintf("%d", time.Now().Unix()) + data["gntoken"] = c.generateSignature(data) + + url := "https://api.gname.com" + path + req := c.client.R(). + SetHeader("Content-Type", "application/x-www-form-urlencoded"). + SetFormData(data) + resp, err := req.Post(url) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + + if resp.IsError() { + return nil, fmt.Errorf("unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) + } + + return resp, nil +} + +func (c *GnameClient) sendRequestWithResult(path string, params map[string]any, result BaseResponse) error { + resp, err := c.sendRequest(path, params) + if err != nil { + return err + } + + jsonResp := make(map[string]any) + if err := json.Unmarshal(resp.Body(), &jsonResp); err != nil { + return fmt.Errorf("failed to parse response: %w", err) + } + if err := maps.Decode(jsonResp, &result); err != nil { + return fmt.Errorf("failed to parse response: %w", err) + } + + if result.GetCode() != 1 { + return fmt.Errorf("API error: %s", result.GetMsg()) + } + + return nil +} From 0e1a964e7cb360c870fd223848c0f466b4ebdc26 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 24 Jan 2025 03:42:34 +0800 Subject: [PATCH 11/11] feat: add gname applicant --- README.md | 1 + README_EN.md | 1 + internal/applicant/providers.go | 17 ++ internal/domain/access.go | 5 + internal/domain/provider.go | 2 + .../acme-dns-01/lego-providers/gname/gname.go | 40 ++++ .../lego-providers/gname/internal/lego.go | 196 ++++++++++++++++++ internal/pkg/vendors/gname-sdk/api.go | 48 ++--- internal/pkg/vendors/gname-sdk/client.go | 18 +- ui/public/imgs/providers/gname.svg | 1 + ui/public/imgs/providers/rainyun.svg | 11 +- ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormGnameConfig.tsx | 76 +++++++ ui/src/domain/access.ts | 6 + ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 6 + ui/src/i18n/locales/en/nls.common.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 6 + ui/src/i18n/locales/zh/nls.common.json | 1 + 19 files changed, 400 insertions(+), 43 deletions(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go create mode 100644 ui/public/imgs/providers/gname.svg create mode 100644 ui/src/components/access/AccessFormGnameConfig.tsx diff --git a/README.md b/README.md index 97e851ea..b7cfc56e 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ make local.run | [Azure](https://azure.microsoft.com/) | | | [CloudFlare](https://www.cloudflare.com/) | | | [ClouDNS](https://www.cloudns.net//) | | +| [GNAME](https://www.gname.com/) | | | [GoDaddy](https://www.godaddy.com/) | | | [Name.com](https://www.name.com/) | | | [NameSilo](https://www.namesilo.com/) | | diff --git a/README_EN.md b/README_EN.md index b5c71b3d..c2174fad 100644 --- a/README_EN.md +++ b/README_EN.md @@ -95,6 +95,7 @@ The following DNS providers are supported: | [Azure DNS](https://azure.microsoft.com/) | | | [CloudFlare](https://www.cloudflare.com/) | | | [ClouDNS](https://www.cloudns.net//) | | +| [GNAME](https://www.gname.com/) | | | [GoDaddy](https://www.godaddy.com/) | | | [Name.com](https://www.name.com/) | | | [NameSilo](https://www.namesilo.com/) | | diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 8329ff54..f53c7287 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -12,6 +12,7 @@ import ( providerAzureDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns" providerCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare" providerClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns" + providerGname "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname" providerGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy" providerHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud" providerNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom" @@ -131,6 +132,22 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } + case domain.ApplyDNSProviderTypeGname: + { + access := domain.AccessConfigForGname{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to decode provider access config: %w", err) + } + + applicant, err := providerGname.NewChallengeProvider(&providerGname.GnameApplicantConfig{ + AppId: access.AppId, + AppKey: access.AppKey, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } + case domain.ApplyDNSProviderTypeGoDaddy: { access := domain.AccessConfigForGoDaddy{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 70128622..c8448c21 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -78,6 +78,11 @@ type AccessConfigForEdgio struct { ClientSecret string `json:"clientSecret"` } +type AccessConfigForGname struct { + AppId string `json:"appId"` + AppKey string `json:"appKey"` +} + type AccessConfigForGoDaddy struct { ApiKey string `json:"apiKey"` ApiSecret string `json:"apiSecret"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 374fab2c..894f8007 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -19,6 +19,7 @@ const ( AccessProviderTypeClouDNS = AccessProviderType("cloudns") AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") AccessProviderTypeEdgio = AccessProviderType("edgio") + AccessProviderTypeGname = AccessProviderType("gname") AccessProviderTypeGoDaddy = AccessProviderType("godaddy") AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud") AccessProviderTypeKubernetes = AccessProviderType("k8s") @@ -55,6 +56,7 @@ const ( ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns") ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare") ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns") + ApplyDNSProviderTypeGname = ApplyDNSProviderType("gname") ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy") ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType("huaweicloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS] ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns") diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go new file mode 100644 index 00000000..90cec017 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/gname.go @@ -0,0 +1,40 @@ +package gname + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/challenge" + + internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal" +) + +type GnameApplicantConfig struct { + AppId string `json:"appId"` + AppKey string `json:"appKey"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *GnameApplicantConfig) (challenge.Provider, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + providerConfig := internal.NewDefaultConfig() + providerConfig.AppID = config.AppId + providerConfig.AppKey = config.AppKey + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := internal.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go new file mode 100644 index 00000000..979a803b --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go @@ -0,0 +1,196 @@ +package lego_gname + +import ( + "errors" + "fmt" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + + gnamesdk "github.com/usual2970/certimate/internal/pkg/vendors/gname-sdk" +) + +const ( + envNamespace = "GNAME_" + + EnvAppID = envNamespace + "APP_ID" + EnvAppKey = envNamespace + "APP_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +type Config struct { + AppID string + AppKey string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPTimeout time.Duration +} + +type DNSProvider struct { + client *gnamesdk.GnameClient + config *Config +} + +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + } +} + +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAppID, EnvAppKey) + if err != nil { + return nil, fmt.Errorf("gname: %w", err) + } + + config := NewDefaultConfig() + config.AppID = values[EnvAppID] + config.AppKey = values[EnvAppKey] + + return NewDNSProviderConfig(config) +} + +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("gname: the configuration of the DNS provider is nil") + } + + client := gnamesdk.NewGnameClient(config.AppID, config.AppKey). + WithTimeout(config.HTTPTimeout) + + return &DNSProvider{ + client: client, + config: config, + }, nil +} + +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + zoneName, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("gname: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zoneName) + if err != nil { + return fmt.Errorf("gname: %w", err) + } + + if err := d.addOrUpdateDNSRecord(domain, subDomain, info.Value); err != nil { + return fmt.Errorf("gname: %w", err) + } + + return nil +} + +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + subDomain := dns01.UnFqdn(fqdn) + + if err := d.removeDNSRecord(domain, subDomain, value); err != nil { + return fmt.Errorf("gname: %w", err) + } + + return nil +} + +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func (d *DNSProvider) getDNSRecord(domain, subDomain string) (*gnamesdk.ResolutionRecord, error) { + page := 1 + pageSize := 20 + for { + request := &gnamesdk.ListDomainResolutionRequest{} + request.ZoneName = domain + request.Page = &page + request.PageSize = &pageSize + + response, err := d.client.ListDomainResolution(request) + if err != nil { + return nil, err + } + + for _, record := range response.Data { + if record.RecordType == "TXT" && record.RecordName == subDomain { + return record, nil + } + } + + if len(response.Data) == 0 { + break + } + if response.Page*response.PageSize >= response.Count { + break + } + + page++ + } + + return nil, nil +} + +func (d *DNSProvider) addOrUpdateDNSRecord(domain, subDomain, value string) error { + record, err := d.getDNSRecord(domain, subDomain) + if err != nil { + return err + } + + if record == nil { + request := &gnamesdk.AddDomainResolutionRequest{ + ZoneName: domain, + RecordType: "TXT", + RecordName: subDomain, + RecordValue: value, + TTL: d.config.TTL, + } + _, err := d.client.AddDomainResolution(request) + return err + } else { + request := &gnamesdk.ModifyDomainResolutionRequest{ + ID: record.ID, + ZoneName: domain, + RecordType: "TXT", + RecordName: subDomain, + RecordValue: value, + TTL: d.config.TTL, + } + _, err := d.client.ModifyDomainResolution(request) + return err + } + + return nil +} + +func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error { + record, err := d.getDNSRecord(domain, subDomain) + if err != nil { + return err + } + + if record == nil { + return nil + } + + request := &gnamesdk.DeleteDomainResolutionRequest{ + ZoneName: domain, + RecordID: record.ID, + } + _, err = d.client.DeleteDomainResolution(request) + return err +} diff --git a/internal/pkg/vendors/gname-sdk/api.go b/internal/pkg/vendors/gname-sdk/api.go index bb35f2e5..33972adc 100644 --- a/internal/pkg/vendors/gname-sdk/api.go +++ b/internal/pkg/vendors/gname-sdk/api.go @@ -5,7 +5,7 @@ type BaseResponse interface { GetMsg() string } -type AddDNSRecordRequest struct { +type AddDomainResolutionRequest struct { ZoneName string `json:"ym"` RecordType string `json:"lx"` RecordName string `json:"zj"` @@ -14,21 +14,21 @@ type AddDNSRecordRequest struct { TTL int `json:"ttl"` } -type AddDNSRecordResponse struct { +type AddDomainResolutionResponse struct { Code int `json:"code"` Msg string `json:"msg"` Data int `json:"data"` } -func (r *AddDNSRecordResponse) GetCode() int { +func (r *AddDomainResolutionResponse) GetCode() int { return r.Code } -func (r *AddDNSRecordResponse) GetMsg() string { +func (r *AddDomainResolutionResponse) GetMsg() string { return r.Msg } -type EditDNSRecordRequest struct { +type ModifyDomainResolutionRequest struct { ID string `json:"jxid"` ZoneName string `json:"ym"` RecordType string `json:"lx"` @@ -38,53 +38,53 @@ type EditDNSRecordRequest struct { TTL int `json:"ttl"` } -type EditDNSRecordResponse struct { +type ModifyDomainResolutionResponse struct { Code int `json:"code"` Msg string `json:"msg"` } -func (r *EditDNSRecordResponse) GetCode() int { +func (r *ModifyDomainResolutionResponse) GetCode() int { return r.Code } -func (r *EditDNSRecordResponse) GetMsg() string { +func (r *ModifyDomainResolutionResponse) GetMsg() string { return r.Msg } -type DeleteDNSRecordRequest struct { +type DeleteDomainResolutionRequest struct { ZoneName string `json:"ym"` - RecordId int `json:"jxid"` + RecordID string `json:"jxid"` } -type DeleteDNSRecordResponse struct { +type DeleteDomainResolutionResponse struct { Code int `json:"code"` Msg string `json:"msg"` } -func (r *DeleteDNSRecordResponse) GetCode() int { +func (r *DeleteDomainResolutionResponse) GetCode() int { return r.Code } -func (r *DeleteDNSRecordResponse) GetMsg() string { +func (r *DeleteDomainResolutionResponse) GetMsg() string { return r.Msg } -type ListDNSRecordRequest struct { +type ListDomainResolutionRequest struct { ZoneName string `json:"ym"` Page *int `json:"page,omitempty"` PageSize *int `json:"limit,omitempty"` } -type ListDNSRecordResponse struct { - Code int `json:"code"` - Msg string `json:"msg"` - Count int `json:"count"` - Data []*DNSRecord `json:"data"` - Page int `json:"page"` - PageSize int `json:"pagesize"` +type ListDomainResolutionResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Count int `json:"count"` + Data []*ResolutionRecord `json:"data"` + Page int `json:"page"` + PageSize int `json:"pagesize"` } -type DNSRecord struct { +type ResolutionRecord struct { ID string `json:"id"` ZoneName string `json:"ym"` RecordType string `json:"lx"` @@ -93,10 +93,10 @@ type DNSRecord struct { MX int `json:"mx"` } -func (r *ListDNSRecordResponse) GetCode() int { +func (r *ListDomainResolutionResponse) GetCode() int { return r.Code } -func (r *ListDNSRecordResponse) GetMsg() string { +func (r *ListDomainResolutionResponse) GetMsg() string { return r.Msg } diff --git a/internal/pkg/vendors/gname-sdk/client.go b/internal/pkg/vendors/gname-sdk/client.go index 81812682..d034cfeb 100644 --- a/internal/pkg/vendors/gname-sdk/client.go +++ b/internal/pkg/vendors/gname-sdk/client.go @@ -35,12 +35,12 @@ func (c *GnameClient) WithTimeout(timeout time.Duration) *GnameClient { return c } -func (c *GnameClient) AddDNSRecord(req *AddDNSRecordRequest) (*AddDNSRecordResponse, error) { +func (c *GnameClient) AddDomainResolution(req *AddDomainResolutionRequest) (*AddDomainResolutionResponse, error) { params := make(map[string]any) jsonData, _ := json.Marshal(req) json.Unmarshal(jsonData, ¶ms) - result := AddDNSRecordResponse{} + result := AddDomainResolutionResponse{} err := c.sendRequestWithResult("/api/resolution/add", params, &result) if err != nil { return nil, err @@ -48,12 +48,12 @@ func (c *GnameClient) AddDNSRecord(req *AddDNSRecordRequest) (*AddDNSRecordRespo return &result, nil } -func (c *GnameClient) EditDNSRecord(req *EditDNSRecordRequest) (*EditDNSRecordResponse, error) { +func (c *GnameClient) ModifyDomainResolution(req *ModifyDomainResolutionRequest) (*ModifyDomainResolutionResponse, error) { params := make(map[string]any) jsonData, _ := json.Marshal(req) json.Unmarshal(jsonData, ¶ms) - result := EditDNSRecordResponse{} + result := ModifyDomainResolutionResponse{} err := c.sendRequestWithResult("/api/resolution/edit", params, &result) if err != nil { return nil, err @@ -61,12 +61,12 @@ func (c *GnameClient) EditDNSRecord(req *EditDNSRecordRequest) (*EditDNSRecordRe return &result, nil } -func (c *GnameClient) DeleteDNSRecord(req *DeleteDNSRecordRequest) (*DeleteDNSRecordResponse, error) { +func (c *GnameClient) DeleteDomainResolution(req *DeleteDomainResolutionRequest) (*DeleteDomainResolutionResponse, error) { params := make(map[string]any) jsonData, _ := json.Marshal(req) json.Unmarshal(jsonData, ¶ms) - result := DeleteDNSRecordResponse{} + result := DeleteDomainResolutionResponse{} err := c.sendRequestWithResult("/api/resolution/delete", params, &result) if err != nil { return nil, err @@ -74,12 +74,12 @@ func (c *GnameClient) DeleteDNSRecord(req *DeleteDNSRecordRequest) (*DeleteDNSRe return &result, nil } -func (c *GnameClient) ListDNSRecord(req *ListDNSRecordRequest) (*ListDNSRecordResponse, error) { +func (c *GnameClient) ListDomainResolution(req *ListDomainResolutionRequest) (*ListDomainResolutionResponse, error) { params := make(map[string]any) jsonData, _ := json.Marshal(req) json.Unmarshal(jsonData, ¶ms) - result := ListDNSRecordResponse{} + result := ListDomainResolutionResponse{} err := c.sendRequestWithResult("/api/resolution/list", params, &result) if err != nil { return nil, err @@ -124,7 +124,7 @@ func (c *GnameClient) sendRequest(path string, params map[string]any) (*resty.Re data["gntime"] = fmt.Sprintf("%d", time.Now().Unix()) data["gntoken"] = c.generateSignature(data) - url := "https://api.gname.com" + path + url := "http://api.gname.com" + path req := c.client.R(). SetHeader("Content-Type", "application/x-www-form-urlencoded"). SetFormData(data) diff --git a/ui/public/imgs/providers/gname.svg b/ui/public/imgs/providers/gname.svg new file mode 100644 index 00000000..ec409ca5 --- /dev/null +++ b/ui/public/imgs/providers/gname.svg @@ -0,0 +1 @@ + diff --git a/ui/public/imgs/providers/rainyun.svg b/ui/public/imgs/providers/rainyun.svg index 18413e16..1076cb76 100644 --- a/ui/public/imgs/providers/rainyun.svg +++ b/ui/public/imgs/providers/rainyun.svg @@ -1,10 +1 @@ - - - - - - - - - - + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index e007eb89..c6c44d2d 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -19,6 +19,7 @@ import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig"; import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig"; import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig"; import AccessFormEdgioConfig from "./AccessFormEdgioConfig"; +import AccessFormGnameConfig from "./AccessFormGnameConfig"; import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig"; import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig"; @@ -106,6 +107,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.DOGECLOUD: return ; + case ACCESS_PROVIDERS.GNAME: + return ; case ACCESS_PROVIDERS.GODADDY: return ; case ACCESS_PROVIDERS.EDGIO: diff --git a/ui/src/components/access/AccessFormGnameConfig.tsx b/ui/src/components/access/AccessFormGnameConfig.tsx new file mode 100644 index 00000000..f0c8f072 --- /dev/null +++ b/ui/src/components/access/AccessFormGnameConfig.tsx @@ -0,0 +1,76 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForGname } from "@/domain/access"; + +type AccessFormGnameConfigFieldValues = Nullish; + +export type AccessFormGnameConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormGnameConfigFieldValues; + onValuesChange?: (values: AccessFormGnameConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormGnameConfigFieldValues => { + return { + appId: "", + appKey: "", + }; +}; + +const AccessFormGnameConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormGnameConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + appId: z + .string() + .min(1, t("access.form.gname_app_id.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + appKey: z + .string() + .min(1, t("access.form.gname_app_key.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormGnameConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 14b31be6..1e229751 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -18,6 +18,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForClouDNS | AccessConfigForDogeCloud | AccessConfigForEdgio + | AccessConfigForGname | AccessConfigForGoDaddy | AccessConfigForHuaweiCloud | AccessConfigForKubernetes @@ -91,6 +92,11 @@ export type AccessConfigForEdgio = { clientSecret: string; }; +export type AccessConfigForGname = { + appId: string; + appKey: string; +}; + export type AccessConfigForGoDaddy = { apiKey: string; apiSecret: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 16fe5da6..652f4332 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -13,6 +13,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ CLOUDFLARE: "cloudflare", CLOUDNS: "cloudns", DOGECLOUD: "dogecloud", + GNAME: "gname", GODADDY: "godaddy", EDGIO: "edgio", HUAWEICLOUD: "huaweicloud", @@ -73,6 +74,7 @@ export const accessProvidersMap: Maphttps://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients", + "access.form.gname_app_id.label": "GNAME AppId", + "access.form.gname_app_id.placeholder": "Please enter GNAME AppId", + "access.form.gname_app_id.tooltip": "For more information, see https://www.gname.com/user#/dealer_api", + "access.form.gname_app_key.label": "GNAME AppKey", + "access.form.gname_app_key.placeholder": "Please enter GNAME AppKey", + "access.form.gname_app_key.tooltip": "For more information, see https://www.gname.com/user#/dealer_api", "access.form.godaddy_api_key.label": "GoDaddy API key", "access.form.godaddy_api_key.placeholder": "Please enter GoDaddy API key", "access.form.godaddy_api_key.tooltip": "For more information, see https://developer.godaddy.com/", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 4fb7e387..cb5c2d41 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -61,6 +61,7 @@ "common.provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)", "common.provider.edgio": "Edgio", "common.provider.edgio.applications": "Edgio - Applications", + "common.provider.gname": "GNAME", "common.provider.godaddy": "GoDaddy", "common.provider.huaweicloud": "Huawei Cloud", "common.provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 6fce9673..d0c9037d 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -90,6 +90,12 @@ "access.form.edgio_client_secret.label": "Edgio 客户端密码", "access.form.edgio_client_secret.placeholder": "请输入 Edgio 客户端密码", "access.form.edgio_client_secret.tooltip": "这是什么?请参阅 https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients", + "access.form.gname_app_id.label": "GNAME AppId", + "access.form.gname_app_id.placeholder": "请输入 GNAME AppId", + "access.form.gname_app_id.tooltip": "这是什么?请参阅 https://www.gname.com/user#/dealer_api", + "access.form.gname_app_key.label": "GNAME AppKey", + "access.form.gname_app_key.placeholder": "请输入 GNAME AppKey", + "access.form.gname_app_key.tooltip": "这是什么?请参阅 https://www.gname.com/user#/dealer_api", "access.form.godaddy_api_key.label": "GoDaddy API Key", "access.form.godaddy_api_key.placeholder": "请输入 GoDaddy API Key", "access.form.godaddy_api_key.tooltip": "这是什么?请参阅 https://developer.godaddy.com/", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 3035e533..b8b97842 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -61,6 +61,7 @@ "common.provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN", "common.provider.edgio": "Edgio", "common.provider.edgio.applications": "Edgio - Applications", + "common.provider.gname": "GNAME", "common.provider.godaddy": "GoDaddy", "common.provider.huaweicloud": "华为云", "common.provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",