From c2a2ad6cb0404803ec5583cac2db2de3f172316a Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 22 Oct 2025 22:25:07 +0800 Subject: [PATCH] feat(provider): new acme dns-01 provider: rfc2136 --- internal/certapply/applicators/sp_rfc2136.go | 33 ++++++ internal/domain/access.go | 26 +++-- internal/domain/provider.go | 3 +- .../acme-dns01/providers/rfc2136/rfc2136.go | 55 ++++++++++ ui/public/imgs/providers/rfc.png | Bin 0 -> 288 bytes ui/src/components/access/AccessForm.tsx | 4 + .../AccessConfigFieldsProviderACMEHttpReq.tsx | 4 +- .../AccessConfigFieldsProviderRFC2136.tsx | 103 ++++++++++++++++++ ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 10 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 10 ++ ui/src/i18n/locales/zh/nls.provider.json | 1 + 13 files changed, 242 insertions(+), 12 deletions(-) create mode 100644 internal/certapply/applicators/sp_rfc2136.go create mode 100644 pkg/core/ssl-applicator/acme-dns01/providers/rfc2136/rfc2136.go create mode 100644 ui/public/imgs/providers/rfc.png create mode 100644 ui/src/components/access/forms/AccessConfigFieldsProviderRFC2136.tsx diff --git a/internal/certapply/applicators/sp_rfc2136.go b/internal/certapply/applicators/sp_rfc2136.go new file mode 100644 index 00000000..025a6928 --- /dev/null +++ b/internal/certapply/applicators/sp_rfc2136.go @@ -0,0 +1,33 @@ +package applicators + +import ( + "fmt" + + "github.com/go-acme/lego/v4/challenge" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/rfc2136" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeRFC2136, func(options *ProviderFactoryOptions) (challenge.Provider, error) { + credentials := domain.AccessConfigForRFC2136{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := rfc2136.NewChallengeProvider(&rfc2136.ChallengeProviderConfig{ + Host: credentials.Host, + Port: credentials.Port, + TsigAlgorithm: credentials.TsigAlgorithm, + TsigKey: credentials.TsigKey, + TsigSecret: credentials.TsigSecret, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return provider, err + }); err != nil { + panic(err) + } +} diff --git a/internal/domain/access.go b/internal/domain/access.go index f40834f8..9b3f10bc 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -202,6 +202,15 @@ type AccessConfigForGcore struct { ApiToken string `json:"apiToken"` } +type AccessConfigForGlobalSectigo struct { + AccessConfigForACMEExternalAccountBinding + ValidationType string `json:"validationType"` +} + +type AccessConfigForGlobalSignAtlas struct { + AccessConfigForACMEExternalAccountBinding +} + type AccessConfigForGname struct { AppId string `json:"appId"` AppKey string `json:"appKey"` @@ -220,10 +229,6 @@ type AccessConfigForGoEdge struct { AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } -type AccessConfigForGlobalSignAtlas struct { - AccessConfigForACMEExternalAccountBinding -} - type AccessConfigForGoogleTrustServices struct { AccessConfigForACMEExternalAccountBinding } @@ -335,17 +340,20 @@ type AccessConfigForRatPanel struct { AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } +type AccessConfigForRFC2136 struct { + Host string `json:"host"` + Port int32 `json:"port"` + TsigAlgorithm string `json:"tsigAlgorithm,omitempty"` + TsigKey string `json:"tsigKey,omitempty"` + TsigSecret string `json:"tsigSecret,omitempty"` +} + type AccessConfigForSafeLine struct { ServerUrl string `json:"serverUrl"` ApiToken string `json:"apiToken"` AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } -type AccessConfigForGlobalSectigo struct { - AccessConfigForACMEExternalAccountBinding - ValidationType string `json:"validationType"` -} - type AccessConfigForSlackBot struct { BotToken string `json:"botToken"` ChannelId string `json:"channelId,omitempty"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 0937d998..c51b9430 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -79,7 +79,7 @@ const ( AccessProviderTypeQingCloud = AccessProviderType("qingcloud") // 青云(预留) AccessProviderTypeRainYun = AccessProviderType("rainyun") AccessProviderTypeRatPanel = AccessProviderType("ratpanel") - AccessProviderTypeRFC2136 = AccessProviderType("rfc2136") // RFC2136(预留) + AccessProviderTypeRFC2136 = AccessProviderType("rfc2136") AccessProviderTypeSafeLine = AccessProviderType("safeline") AccessProviderTypeSectigo = AccessProviderType("sectigo") AccessProviderTypeSlackBot = AccessProviderType("slackbot") @@ -174,6 +174,7 @@ const ( ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun) ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS) ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun) + ACMEDns01ProviderTypeRFC2136 = ACMEDns01ProviderType(AccessProviderTypeRFC2136) ACMEDns01ProviderTypeSpaceship = ACMEDns01ProviderType(AccessProviderTypeSpaceship) ACMEDns01ProviderTypeTechnitiumDNS = ACMEDns01ProviderType(AccessProviderTypeTechnitiumDNS) ACMEDns01ProviderTypeTencentCloud = ACMEDns01ProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeTencentCloudDNS] diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/rfc2136/rfc2136.go b/pkg/core/ssl-applicator/acme-dns01/providers/rfc2136/rfc2136.go new file mode 100644 index 00000000..8ff193ca --- /dev/null +++ b/pkg/core/ssl-applicator/acme-dns01/providers/rfc2136/rfc2136.go @@ -0,0 +1,55 @@ +package rfc2136 + +import ( + "errors" + "net" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/rfc2136" + + "github.com/certimate-go/certimate/pkg/core" +) + +type ChallengeProviderConfig struct { + Host string `json:"host"` + Port int32 `json:"port"` + TsigAlgorithm string `json:"tsigAlgorithm,omitempty"` + TsigKey string `json:"tsigKey,omitempty"` + TsigSecret string `json:"tsigSecret,omitempty"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) { + if config == nil { + return nil, errors.New("the configuration of the acme challenge provider is nil") + } + + if config.Port == 0 { + config.Port = 53 + } + + if config.TsigAlgorithm == "" { + config.TsigAlgorithm = "hmac-sha1." + } + + providerConfig := rfc2136.NewDefaultConfig() + providerConfig.Nameserver = net.JoinHostPort(config.Host, strconv.Itoa(int(config.Port))) + providerConfig.TSIGAlgorithm = config.TsigAlgorithm + providerConfig.TSIGKey = config.TsigKey + providerConfig.TSIGSecret = config.TsigSecret + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := rfc2136.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/rfc.png b/ui/public/imgs/providers/rfc.png new file mode 100644 index 0000000000000000000000000000000000000000..90f5c6296c05df4230b830fd292c15ee15f06f0e GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSK$uZf!>a)(xYg6eF(ktMZQn(%76TrZ-~Z>o@m?P^&1LT9 zk0D2txt*PZ+H|-2ef8YrAa;T&U!%b6iAfu)mnQd=6Z_itJ}O}BRNlQWeLaWxjLqh+ z@1A=j`j%au#e4eZL*?JPzBThVEq%-+_N~jC>%zH1aTn`8J9_MzcKP0UpwM^m3u~_b zf0+o<`Z+f&!`<)vf3XcS_b&STpgG}X9V5S`t-WZ(% c6O*t?{-%q4xqE^<(02?Bp00i_>zopr00Z@I?EnA( literal 0 HcmV?d00001 diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 383351d9..2430221b 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -70,6 +70,7 @@ import AccessConfigFieldsProviderProxmoxVE from "./forms/AccessConfigFieldsProvi import AccessConfigFieldsProviderQiniu from "./forms/AccessConfigFieldsProviderQiniu"; import AccessConfigFieldsProviderRainYun from "./forms/AccessConfigFieldsProviderRainYun"; import AccessConfigFieldsProviderRatPanel from "./forms/AccessConfigFieldsProviderRatPanel"; +import AccessConfigFieldsProviderRFC2136 from "./forms/AccessConfigFieldsProviderRFC2136"; import AccessConfigFieldsProviderSafeLine from "./forms/AccessConfigFieldsProviderSafeLine"; import AccessConfigFieldsProviderSectigo from "./forms/AccessConfigFieldsProviderSectigo"; import AccessConfigFieldsProviderSlackBot from "./forms/AccessConfigFieldsProviderSlackBot"; @@ -311,6 +312,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, on case ACCESS_PROVIDERS.RATPANEL: { return ; } + case ACCESS_PROVIDERS.RFC2136: { + return ; + } case ACCESS_PROVIDERS.SAFELINE: { return ; } diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderACMEHttpReq.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderACMEHttpReq.tsx index 98cf694f..624273db 100644 --- a/ui/src/components/access/forms/AccessConfigFieldsProviderACMEHttpReq.tsx +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderACMEHttpReq.tsx @@ -36,8 +36,8 @@ const AccessConfigFormFieldsProviderACMEHttpReq = () => { > + + + +
+ + + +
+ + + + + + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + host: "127.0.0.1", + port: 53, + tsigAlgorithm: "hmac-sha1.", + tsigKey: "", + tsigSecret: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), + port: z.preprocess( + (v) => Number(v), + z + .number() + .int(t("access.form.rfc2136_port.placeholder")) + .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) + ), + tsigAlgorithm: z.string().nonempty(t("access.form.rfc2136_tsig_algorithm.placeholder")), + tsigKey: z.string().nullish(), + tsigSecret: z.string().nullish(), + }); +}; + +const _default = Object.assign(AccessConfigFormFieldsProviderRFC2136, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 3d0526e5..4ef6760e 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -77,6 +77,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ QINIU: "qiniu", RAINYUN: "rainyun", RATPANEL: "ratpanel", + RFC2136: "rfc2136", SAFELINE: "safeline", SECTIGO: "sectigo", SLACKBOT: "slackbot", @@ -187,6 +188,7 @@ export const accessProvidersMap: Map diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 66b5faab..0ba323e0 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -405,6 +405,16 @@ "access.form.ratpanel_access_token.label": "RatPanel access token", "access.form.ratpanel_access_token.placeholder": "Please enter RatPanel access token", "access.form.ratpanel_access_token.tooltip": "For more information, see https://ratpanel.github.io/advanced/api.html", + "access.form.rfc2136_host.label": "DNS server host", + "access.form.rfc2136_host.placeholder": "Please enter DNS server host", + "access.form.rfc2136_port.label": "DNS server port", + "access.form.rfc2136_port.placeholder": "Please enter DNS server port", + "access.form.rfc2136_tsig_algorithm.label": "TSIG algorithm", + "access.form.rfc2136_tsig_algorithm.placeholder": "Please select TSIG algorithm", + "access.form.rfc2136_tsig_key.label": "TSIG authentication key (Optional)", + "access.form.rfc2136_tsig_key.placeholder": "Please enter TSIG authentication key", + "access.form.rfc2136_tsig_secret.label": "TSIG authentication secret (Optional)", + "access.form.rfc2136_tsig_secret.placeholder": "Please enter TSIG authentication secret", "access.form.safeline_server_url.label": "SafeLine server URL", "access.form.safeline_server_url.placeholder": "Please enter SafeLine server URL", "access.form.safeline_api_token.label": "SafeLine API token", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 0723fef4..fe6c6cae 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -132,6 +132,7 @@ "provider.ratpanel": "RatPanel", "provider.ratpanel.console": "RatPanel - Console itself", "provider.ratpanel.site": "RatPanel - Website", + "provider.rfc2136": "RFC 2136: Dynamic DNS Updates", "provider.safeline": "SafeLine", "provider.sectigo": "Sectigo", "provider.slackbot": "Slack Bot", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index dc8b24fd..08e0906f 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -404,6 +404,16 @@ "access.form.ratpanel_access_token.label": "耗子面板 AccessToken", "access.form.ratpanel_access_token.placeholder": "请输入耗子面板 AccessToken", "access.form.ratpanel_access_token.tooltip": "这是什么?请参阅 https://ratpanel.github.io/advanced/api.html", + "access.form.rfc2136_host.label": "DNS 服务器地址", + "access.form.rfc2136_host.placeholder": "请输入 DNS 服务器地址", + "access.form.rfc2136_port.label": "DNS 服务器端口", + "access.form.rfc2136_port.placeholder": "请输入 DNS 服务器端口", + "access.form.rfc2136_tsig_algorithm.label": "TSIG 算法", + "access.form.rfc2136_tsig_algorithm.placeholder": "请选择 TSIG 算法", + "access.form.rfc2136_tsig_key.label": "TSIG 认证密钥 Key(可选)", + "access.form.rfc2136_tsig_key.placeholder": "请输入 TSIG 认证密钥 Key", + "access.form.rfc2136_tsig_secret.label": "TSIG 认证密钥 Secret(可选)", + "access.form.rfc2136_tsig_secret.placeholder": "请输入 TSIG 认证密钥 Secret", "access.form.safeline_server_url.label": "雷池服务地址", "access.form.safeline_server_url.placeholder": "请输入雷池服务地址", "access.form.safeline_api_token.label": "雷池 API Token", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 8ec4657e..3aecf1f8 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -132,6 +132,7 @@ "provider.ratpanel": "耗子面板", "provider.ratpanel.console": "耗子面板 - 面板自身", "provider.ratpanel.site": "耗子面板 - 网站", + "provider.rfc2136": "RFC 2136: Dynamic DNS Updates", "provider.safeline": "雷池", "provider.sectigo": "Sectigo", "provider.slackbot": "Slack 机器人",