From 6c3c29dd1117184ba112f2bb2db2c4d7464e7732 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 15:41:22 +0800 Subject: [PATCH] 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": "钉钉",