From f79b028d0b34e94d0ed2efdb0ff9edcdf1d0cfe2 Mon Sep 17 00:00:00 2001 From: imlonghao Date: Sat, 5 Jul 2025 14:07:24 +0800 Subject: [PATCH 1/3] feat: support custom certificate lifetime --- go.mod | 1 + go.sum | 2 ++ internal/applicant/applicant.go | 9 +++++++++ internal/applicant/providers.go | 1 + internal/domain/workflow.go | 2 ++ .../workflow/node/ApplyNodeConfigForm.tsx | 17 +++++++++++++++++ ui/src/domain/workflow.ts | 1 + ui/src/i18n/locales/en/nls.workflow.nodes.json | 3 +++ ui/src/i18n/locales/zh/nls.workflow.nodes.json | 3 +++ 9 files changed, 39 insertions(+) diff --git a/go.mod b/go.mod index 712a99045..f17c5aff9 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.17 github.com/volcengine/volc-sdk-golang v1.0.212 github.com/volcengine/volcengine-go-sdk v1.1.18 + github.com/xhit/go-str2duration/v2 v2.1.0 gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 golang.org/x/crypto v0.39.0 diff --git a/go.sum b/go.sum index 149f91516..3dc87db12 100644 --- a/go.sum +++ b/go.sum @@ -901,6 +901,8 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index c6d5608d6..6be0ca775 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -16,6 +16,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/lego" + "github.com/xhit/go-str2duration/v2" "golang.org/x/exp/slices" "golang.org/x/time/rate" @@ -64,6 +65,7 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err CAProviderAccessConfig: make(map[string]any), CAProviderServiceConfig: nodeCfg.CAProviderConfig, KeyAlgorithm: nodeCfg.KeyAlgorithm, + LifeTime: nodeCfg.LifeTime, ACMEProfile: nodeCfg.ACMEProfile, Nameservers: xslices.Filter(strings.Split(nodeCfg.Nameservers, ";"), func(s string) bool { return s != "" }), DnsPropagationWait: nodeCfg.DnsPropagationWait, @@ -238,6 +240,13 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt Bundle: true, Profile: options.ACMEProfile, } + if options.LifeTime != "" { + lifeTime, err := str2duration.ParseDuration(options.LifeTime) + if err != nil { + return nil, fmt.Errorf("invalid lifetime: %w", err) + } + certRequest.NotAfter = time.Now().Add(lifeTime) + } if options.ARIReplaceAcct == user.Registration.URI { certRequest.ReplacesCertID = options.ARIReplaceCert } diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 7b07997fe..b1f147c1e 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -63,6 +63,7 @@ type applicantProviderOptions struct { DnsPropagationWait int32 DnsPropagationTimeout int32 DnsTTL int32 + LifeTime string ACMEProfile string DisableFollowCNAME bool ARIReplaceAcct string diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 16895229a..c51f513a9 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -75,6 +75,7 @@ type WorkflowNodeConfigForApply struct { CAProviderAccessId string `json:"caProviderAccessId,omitempty"` // CA 提供商授权记录 ID CAProviderConfig map[string]any `json:"caProviderConfig,omitempty"` // CA 提供商额外配置 KeyAlgorithm string `json:"keyAlgorithm,omitempty"` // 证书算法 + LifeTime string `json:"lifeTime,omitempty"` // 证书有效期 ACMEProfile string `json:"acmeProfile,omitempty"` // ACME Profiles Extension Nameservers string `json:"nameservers,omitempty"` // DNS 服务器列表,以半角分号分隔 DnsPropagationWait int32 `json:"dnsPropagationWait,omitempty"` // DNS 传播等待时间,等同于 lego 的 `--dns-propagation-wait` 参数 @@ -132,6 +133,7 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { CAProviderAccessId: xmaps.GetString(n.Config, "caProviderAccessId"), CAProviderConfig: xmaps.GetKVMapAny(n.Config, "caProviderConfig"), KeyAlgorithm: xmaps.GetOrDefaultString(n.Config, "keyAlgorithm", string(CertificateKeyAlgorithmTypeRSA2048)), + LifeTime: xmaps.GetString(n.Config, "lifeTime"), ACMEProfile: xmaps.GetString(n.Config, "acmeProfile"), Nameservers: xmaps.GetString(n.Config, "nameservers"), DnsPropagationWait: xmaps.GetInt32(n.Config, "dnsPropagationWait"), diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index cdcb689c4..43df598df 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -113,6 +113,10 @@ const ApplyNodeConfigForm = forwardRef (v == null || v === "" ? undefined : Number(v)), z.number().int(t("workflow_node.apply.form.dns_ttl.placeholder")).gte(1, t("workflow_node.apply.form.dns_ttl.placeholder")).nullish() ), + lifeTime: z.string().nullish().refine((v) => { + if (!v) return true; + return /^[+-]?(\d+[ns|us|ms|s|m|h|d|w])+$/.test(v); + }, t("workflow_node.apply.form.life_time.placeholder")), acmeProfile: z.string().nullish(), disableFollowCNAME: z.boolean().nullish(), disableARI: z.boolean().nullish(), @@ -456,6 +460,19 @@ const ApplyNodeConfigForm = forwardRef + } + > + + + ; keyAlgorithm: string; + lifeTime?: string; acmeProfile?: string; nameservers?: string; dnsPropagationTimeout?: number; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 122542213..7c6b10f55 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -71,6 +71,9 @@ "workflow_node.apply.form.ca_provider_access.button": "Create", "workflow_node.apply.form.key_algorithm.label": "Certificate key algorithm", "workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm", + "workflow_node.apply.form.life_time.label": "Certificate lifetime (Optional)", + "workflow_node.apply.form.life_time.placeholder": "Please enter certificate lifetime, unit support: ns, us, ms, s, m, h, d, w. e.g. 30d", + "workflow_node.apply.form.life_time.tooltip": "It determines the lifetime of the certificate. If you don't understand this option, just keep it by default.", "workflow_node.apply.form.acme_profile.label": "ACME certificate profile (Optional)", "workflow_node.apply.form.acme_profile.placeholder": "Please enter ACME certificate profile", "workflow_node.apply.form.acme_profile.tooltip": "It determines the ACME profile which will be used to affect issuance of the certificate requested. If you don't understand this option, just keep it by default.Learn more.", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index a3f4278e7..ae1125738 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -70,6 +70,9 @@ "workflow_node.apply.form.ca_provider_access.button": "新建", "workflow_node.apply.form.key_algorithm.label": "证书算法", "workflow_node.apply.form.key_algorithm.placeholder": "请选择证书算法", + "workflow_node.apply.form.life_time.label": "证书有效期(可选)", + "workflow_node.apply.form.life_time.placeholder": "请输入证书有效期,单位支持:ns、us、ms、s、m、h、d、w,例如:30d", + "workflow_node.apply.form.life_time.tooltip": "表示证书的有效期。如果你不了解该选项的用途,保持默认即可。", "workflow_node.apply.form.acme_profile.label": "ACME 证书配置(可选)", "workflow_node.apply.form.acme_profile.placeholder": "请输入 ACME 证书配置", "workflow_node.apply.form.acme_profile.tooltip": "表示证书颁发时使用的 ACME 证书配置。如果你不了解该选项的用途,保持默认即可。点此了解更多。", From 43acea2e77ed6866825ef9b532ad4301c19d2ed6 Mon Sep 17 00:00:00 2001 From: "Aldrich J. Xing" Date: Fri, 15 Aug 2025 01:45:39 +0800 Subject: [PATCH 2/3] support Provider ACME DNS in UI. --- ui/public/imgs/providers/acmedns.svg | 2 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormACMEDNSConfig.tsx | 85 +++++++++++++++++++ ui/src/domain/access.ts | 7 ++ ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 9 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 9 ++ ui/src/i18n/locales/zh/nls.provider.json | 1 + 9 files changed, 121 insertions(+) create mode 100644 ui/public/imgs/providers/acmedns.svg create mode 100644 ui/src/components/access/AccessFormACMEDNSConfig.tsx diff --git a/ui/public/imgs/providers/acmedns.svg b/ui/public/imgs/providers/acmedns.svg new file mode 100644 index 000000000..936ca0771 --- /dev/null +++ b/ui/public/imgs/providers/acmedns.svg @@ -0,0 +1,2 @@ + + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index ba9e1318f..204eb6e2f 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -13,6 +13,7 @@ import { useAntdForm, useAntdFormName } from "@/hooks"; import AccessForm1PanelConfig from "./AccessForm1PanelConfig"; import AccessFormACMECAConfig from "./AccessFormACMECAConfig"; +import AccessFormACMEDNSConfig from "./AccessFormACMEDNSConfig"; import AccessFormACMEHttpReqConfig from "./AccessFormACMEHttpReqConfig"; import AccessFormAliyunConfig from "./AccessFormAliyunConfig"; import AccessFormAPISIXConfig from "./AccessFormAPISIXConfig"; @@ -193,6 +194,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.ACMECA: return ; + case ACCESS_PROVIDERS.ACMEDNS: + return ; case ACCESS_PROVIDERS.ACMEHTTPREQ: return ; case ACCESS_PROVIDERS.ALIYUN: diff --git a/ui/src/components/access/AccessFormACMEDNSConfig.tsx b/ui/src/components/access/AccessFormACMEDNSConfig.tsx new file mode 100644 index 000000000..c842d97fd --- /dev/null +++ b/ui/src/components/access/AccessFormACMEDNSConfig.tsx @@ -0,0 +1,85 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod/v4"; + +import { type AccessConfigForACMEDNS } from "@/domain/access"; + +type AccessFormACMEDNSConfigFieldValues = Nullish; + +export interface AccessFormACMEDNSConfigProps { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormACMEDNSConfigFieldValues; + onValuesChange?: (values: AccessFormACMEDNSConfigFieldValues) => void; +} + +const initFormModel = (): AccessFormACMEDNSConfigFieldValues => { + return { + apiBase: "https://auth.acme-dns.io/", + storageBaseUrl: "", + storagePath: "", + }; +}; + +const AccessFormACMEDNSConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormACMEDNSConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiBase: z.url(t("common.errmsg.url_invalid")), + storageBaseUrl: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + storagePath: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormACMEDNSConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 51555f071..fcf5ebaf2 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -8,6 +8,7 @@ export interface AccessModel extends BaseModel { ( | AccessConfigFor1Panel | AccessConfigForACMECA + | AccessConfigForACMEDNS | AccessConfigForACMEHttpReq | AccessConfigForAliyun | AccessConfigForAPISIX @@ -96,6 +97,12 @@ export type AccessConfigForACMECA = { eabHmacKey?: string; }; +export type AccessConfigForACMEDNS = { + apiBase: string; + storageBaseUrl?: string; + storagePath?: string; +}; + export type AccessConfigForACMEHttpReq = { endpoint: string; mode?: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 9c098fc5a..375255366 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -6,6 +6,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ ["1PANEL"]: "1panel", ACMECA: "acmeca", + ACMEDNS: "acmedns", ACMEHTTPREQ: "acmehttpreq", ALIYUN: "aliyun", APISIX: "apisix", @@ -173,6 +174,7 @@ export const accessProvidersMap: Map = new NOTICE: If you add new constant, please keep ASCII order. */ export const ACME_DNS01_PROVIDERS = Object.freeze({ + ACMEDNS: `${ACCESS_PROVIDERS.ACMEDNS}`, ACMEHTTPREQ: `${ACCESS_PROVIDERS.ACMEHTTPREQ}`, ALIYUN: `${ACCESS_PROVIDERS.ALIYUN}`, // 兼容旧值,等同于 `ALIYUN_DNS` ALIYUN_DNS: `${ACCESS_PROVIDERS.ALIYUN}-dns`, @@ -365,6 +368,7 @@ export const acmeDns01ProvidersMap: Map [ type, { diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 0dd233d63..efcf44f66 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -51,6 +51,15 @@ "access.form.acmeca_eab_kid.placeholder": "Please enter ACME EAB KID", "access.form.acmeca_eab_hmac_key.label": "ACME EAB HMAC key (Optional)", "access.form.acmeca_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key", + "access.form.acmedns_api_base.label": "ACME-DNS API URL", + "access.form.acmedns_api_base.placeholder": "Please enter ACME-DNS API URL", + "access.form.acmedns_api_base.tooltip": "For more information, see https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_storage_base_url.label": "ACME-DNS Credentials URL PATH", + "access.form.acmedns_storage_base_url.placeholder": "Enter the URL path to the ACME-DNS JSON credentials JSON file. Each credentials are stored in a separate JSON file. This file will be used for TXT record updates.", + "access.form.acmedns_storage_base_url.tooltip": "For more information, see https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_storage_path.label": "ACME-DNS Credentials Local Path", + "access.form.acmedns_storage_path.placeholder": "Please enter the ACME-DNS JSON Credentials JSON File Path. Each credentials are in a separate JSON file. It will be used for TXT record updates.", + "access.form.acmedns_storage_path.tooltip": "For more information, see https://go-acme.github.io/lego/dns/acme-dns/", "access.form.acmehttpreq_endpoint.label": "Endpoint", "access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint", "access.form.acmehttpreq_endpoint.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 9c7d00535..c227be35e 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -3,6 +3,7 @@ "provider.1panel.console": "1Panel - Console", "provider.1panel.site": "1Panel - Website", "provider.acmeca": "ACME Custom CA Endpoint", + "provider.acmedns": "ACME DNS", "provider.acmehttpreq": "ACME Custom HTTP Endpoint", "provider.aliyun": "Alibaba Cloud", "provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index b6a4a0be2..1a310773e 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -51,6 +51,15 @@ "access.form.acmeca_eab_kid.placeholder": "请输入 ACME EAB KID", "access.form.acmeca_eab_hmac_key.label": "ACME EAB HMAC Key(可选)", "access.form.acmeca_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key", + "access.form.acmedns_api_base.label": "ACME-DNS API 地址", + "access.form.acmedns_api_base.placeholder": "请输入 ACME-DNS API 地址", + "access.form.acmedns_api_base.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_storage_base_url.label": "ACME-DNS JSON 帐户数据服务器", + "access.form.acmedns_storage_base_url.placeholder": "请输入 ACME-DNS JSON 帐户数据服务器地址", + "access.form.acmedns_storage_base_url.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_storage_path.label": "ACME-DNS JSON 帐户数据文件", + "access.form.acmedns_storage_path.placeholder": "ACME-DNS JSON 帐户数据文件。每个域的帐户都将注册/保存到此文件,并用于 TXT 更新。", + "access.form.acmedns_storage_path.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/acme-dns/", "access.form.acmehttpreq_endpoint.label": "服务端点", "access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点", "access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 73a3b63c8..d9baa7d74 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -3,6 +3,7 @@ "provider.1panel.console": "1Panel - 控制台", "provider.1panel.site": "1Panel - 网站", "provider.acmeca": "ACME 自定义 CA 端点", + "provider.acmedns": "ACME DNS", "provider.acmehttpreq": "ACME 自定义 HTTP 端点", "provider.aliyun": "阿里云", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", From 152bdbfcf509432941d52df0ad6486c43df722c2 Mon Sep 17 00:00:00 2001 From: "Aldrich J. Xing" Date: Fri, 15 Aug 2025 01:49:29 +0800 Subject: [PATCH 3/3] support Provider ACME DNS in Backend. --- go.mod | 1 + go.sum | 2 ++ internal/applicant/providers.go | 16 +++++++++ internal/domain/access.go | 6 ++++ internal/domain/provider.go | 2 ++ .../acme-dns01/providers/acmedns/acmedns.go | 35 +++++++++++++++++++ 6 files changed, 62 insertions(+) create mode 100644 pkg/core/ssl-applicator/acme-dns01/providers/acmedns/acmedns.go diff --git a/go.mod b/go.mod index 712a99045..fb1968ebd 100644 --- a/go.mod +++ b/go.mod @@ -123,6 +123,7 @@ require ( github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect github.com/nrdcg/desec v0.10.0 // indirect + github.com/nrdcg/goacmedns v0.2.0 // indirect github.com/nrdcg/mailinabox v0.2.0 // indirect github.com/nrdcg/porkbun v0.4.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect diff --git a/go.sum b/go.sum index 149f91516..d5bb2afb1 100644 --- a/go.sum +++ b/go.sum @@ -692,6 +692,8 @@ github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qu github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8= github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM= github.com/nrdcg/desec v0.10.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= +github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= +github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= 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= diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 7b07997fe..55c4c0fee 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -6,6 +6,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/certimate-go/certimate/internal/domain" + pACMEDNS "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/acmedns" pACMEHttpReq "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/acmehttpreq" pAliyun "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/aliyun" pAliyunESA "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa" @@ -75,6 +76,21 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi NOTICE: If you add new constant, please keep ASCII order. */ switch options.Provider { + case domain.ACMEDns01ProviderTypeACMEDNS: + { + access := domain.AccessConfigForACMEDNS{} + if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + applicant, err := pACMEDNS.NewChallengeProvider(&pACMEDNS.ChallengeProviderConfig{ + ApiBase: access.ApiBase, + StorageBaseUrl: access.StorageBaseUrl, + StoragePath: access.StoragePath, + }) + return applicant, err + } + case domain.ACMEDns01ProviderTypeACMEHttpReq: { access := domain.AccessConfigForACMEHttpReq{} diff --git a/internal/domain/access.go b/internal/domain/access.go index dd63ada1f..6aad4602c 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -28,6 +28,12 @@ type AccessConfigForACMECA struct { EabHmacKey string `json:"eabHmacKey,omitempty"` } +type AccessConfigForACMEDNS struct { + ApiBase string `json:"apiBase"` + StorageBaseUrl string `json:"storageBaseUrl,omitempty"` + StoragePath string `json:"storagePath,omitempty"` +} + type AccessConfigForACMEHttpReq struct { Endpoint string `json:"endpoint"` Mode string `json:"mode,omitempty"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index c6e7b9695..9372026e3 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -11,6 +11,7 @@ type AccessProviderType string const ( AccessProviderType1Panel = AccessProviderType("1panel") AccessProviderTypeACMECA = AccessProviderType("acmeca") + AccessProviderTypeACMEDNS = AccessProviderType("acmedns") AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq") AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留) AccessProviderTypeAliyun = AccessProviderType("aliyun") @@ -121,6 +122,7 @@ ACME DNS-01 提供商常量值。 NOTICE: If you add new constant, please keep ASCII order. */ const ( + ACMEDns01ProviderTypeACMEDNS = ACMEDns01ProviderType(AccessProviderTypeACMEDNS) ACMEDns01ProviderTypeACMEHttpReq = ACMEDns01ProviderType(AccessProviderTypeACMEHttpReq) ACMEDns01ProviderTypeAliyun = ACMEDns01ProviderType(AccessProviderTypeAliyun) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAliyunDNS] ACMEDns01ProviderTypeAliyunDNS = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-dns") diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/acmedns/acmedns.go b/pkg/core/ssl-applicator/acme-dns01/providers/acmedns/acmedns.go new file mode 100644 index 000000000..992766f84 --- /dev/null +++ b/pkg/core/ssl-applicator/acme-dns01/providers/acmedns/acmedns.go @@ -0,0 +1,35 @@ +package acmedns + +import ( + "errors" + "net/url" + + "github.com/go-acme/lego/v4/providers/dns/acmedns" + + "github.com/certimate-go/certimate/pkg/core" +) + +type ChallengeProviderConfig struct { + ApiBase string `json:"apiBase,omitempty"` + StorageBaseUrl string `json:"storageBaseUrl,omitempty"` + StoragePath string `json:"storagePath,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) { + if config == nil { + return nil, errors.New("the configuration of the acme challenge provider is nil") + } + + ApiBase, _ := url.Parse(config.ApiBase) + providerConfig := acmedns.NewDefaultConfig() + providerConfig.APIBase = ApiBase.String() + providerConfig.StorageBaseURL = config.StorageBaseUrl + providerConfig.StoragePath = config.StoragePath + + provider, err := acmedns.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +}