feat(provider): new acme dns-01 provider: technitiumdns

This commit is contained in:
Fu Diwei 2025-10-22 22:24:59 +08:00
parent 34ca09a7fc
commit 1f6ef6dcb3
13 changed files with 199 additions and 14 deletions

View File

@ -0,0 +1,31 @@
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/technitiumdns"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeTechnitiumDNS, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
credentials := domain.AccessConfigForTechnitiumDNS{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := technitiumdns.NewChallengeProvider(&technitiumdns.ChallengeProviderConfig{
ServerUrl: credentials.ServerUrl,
ApiToken: credentials.ApiToken,
AllowInsecureConnections: credentials.AllowInsecureConnections,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return provider, err
}); err != nil {
panic(err)
}
}

View File

@ -379,6 +379,12 @@ type AccessConfigForSSLCom struct {
AccessConfigForACMEExternalAccountBinding
}
type AccessConfigForTechnitiumDNS struct {
ServerUrl string `json:"serverUrl"`
ApiToken string `json:"apiToken"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForTelegramBot struct {
BotToken string `json:"botToken"`
ChatId int64 `json:"chatId,omitempty"`

View File

@ -23,8 +23,9 @@ const (
AccessProviderTypeBaishan = AccessProviderType("baishan")
AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel")
AccessProviderTypeBaotaWAF = AccessProviderType("baotawaf")
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeBookMyName = AccessProviderType("bookmyname") // BookMyName预留
AccessProviderTypeBunny = AccessProviderType("bunny")
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeCacheFly = AccessProviderType("cachefly")
AccessProviderTypeCdnfly = AccessProviderType("cdnfly")
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
@ -52,14 +53,17 @@ const (
AccessProviderTypeGlobalSignAtlas = AccessProviderType("globalsignatlas")
AccessProviderTypeGoogleTrustServices = AccessProviderType("googletrustservices")
AccessProviderTypeHetzner = AccessProviderType("hetzner")
AccessProviderTypeHostinger = AccessProviderType("hostinger") // Hostinger预留
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
AccessProviderTypeKong = AccessProviderType("kong")
AccessProviderTypeKsyun = AccessProviderType("ksyun") // 金山云(预留)
AccessProviderTypeKubernetes = AccessProviderType("k8s")
AccessProviderTypeLarkBot = AccessProviderType("larkbot")
AccessProviderTypeLetsEncrypt = AccessProviderType("letsencrypt")
AccessProviderTypeLetsEncryptStaging = AccessProviderType("letsencryptstaging")
AccessProviderTypeLeCDN = AccessProviderType("lecdn")
AccessProviderTypeLinode = AccessProviderType("linode") // Linode预留
AccessProviderTypeLocal = AccessProviderType("local")
AccessProviderTypeMattermost = AccessProviderType("mattermost")
AccessProviderTypeNamecheap = AccessProviderType("namecheap")
@ -75,12 +79,14 @@ const (
AccessProviderTypeQingCloud = AccessProviderType("qingcloud") // 青云(预留)
AccessProviderTypeRainYun = AccessProviderType("rainyun")
AccessProviderTypeRatPanel = AccessProviderType("ratpanel")
AccessProviderTypeRFC2136 = AccessProviderType("rfc2136") // RFC2136预留
AccessProviderTypeSafeLine = AccessProviderType("safeline")
AccessProviderTypeSectigo = AccessProviderType("sectigo")
AccessProviderTypeSlackBot = AccessProviderType("slackbot")
AccessProviderTypeSpaceship = AccessProviderType("spaceship")
AccessProviderTypeSSH = AccessProviderType("ssh")
AccessProviderTypeSSLCOM = AccessProviderType("sslcom")
AccessProviderTypeTechnitiumDNS = AccessProviderType("technitiumdns")
AccessProviderTypeTelegramBot = AccessProviderType("telegrambot")
AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
AccessProviderTypeUCloud = AccessProviderType("ucloud")
@ -169,6 +175,7 @@ const (
ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS)
ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun)
ACMEDns01ProviderTypeSpaceship = ACMEDns01ProviderType(AccessProviderTypeSpaceship)
ACMEDns01ProviderTypeTechnitiumDNS = ACMEDns01ProviderType(AccessProviderTypeTechnitiumDNS)
ACMEDns01ProviderTypeTencentCloud = ACMEDns01ProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeTencentCloudDNS]
ACMEDns01ProviderTypeTencentCloudDNS = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-dns")
ACMEDns01ProviderTypeTencentCloudEO = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-eo")

View File

@ -3,13 +3,13 @@ package powerdns
import (
"crypto/tls"
"errors"
"net/http"
"net/url"
"time"
"github.com/go-acme/lego/v4/providers/dns/pdns"
"github.com/certimate-go/certimate/pkg/core"
xhttp "github.com/certimate-go/certimate/pkg/utils/http"
)
type ChallengeProviderConfig struct {
@ -30,12 +30,9 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger,
providerConfig.Host = serverUrl
providerConfig.APIKey = config.ApiKey
if config.AllowInsecureConnections {
providerConfig.HTTPClient.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
transport := xhttp.NewDefaultTransport()
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
providerConfig.HTTPClient.Transport = transport
}
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second

View File

@ -0,0 +1,48 @@
package technitiumdns
import (
"crypto/tls"
"errors"
"time"
"github.com/go-acme/lego/v4/providers/dns/technitium"
"github.com/certimate-go/certimate/pkg/core"
xhttp "github.com/certimate-go/certimate/pkg/utils/http"
)
type ChallengeProviderConfig struct {
ServerUrl string `json:"serverUrl"`
ApiToken string `json:"apiToken"`
AllowInsecureConnections bool `json:"allowInsecureConnections,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")
}
providerConfig := technitium.NewDefaultConfig()
providerConfig.BaseURL = config.ServerUrl
providerConfig.APIToken = config.ApiToken
if config.AllowInsecureConnections {
transport := xhttp.NewDefaultTransport()
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
providerConfig.HTTPClient.Transport = transport
}
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := technitium.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

View File

@ -76,6 +76,7 @@ import AccessConfigFieldsProviderSlackBot from "./forms/AccessConfigFieldsProvid
import AccessConfigFieldsProviderSpaceship from "./forms/AccessConfigFieldsProviderSpaceship";
import AccessConfigFieldsProviderSSH from "./forms/AccessConfigFieldsProviderSSH";
import AccessConfigFieldsProviderSSLCom from "./forms/AccessConfigFieldsProviderSSLCom";
import AccessConfigFieldsProviderTechnitiumDNS from "./forms/AccessConfigFieldsProviderTechnitiumDNS";
import AccessConfigFieldsProviderTelegramBot from "./forms/AccessConfigFieldsProviderTelegramBot";
import AccessConfigFieldsProviderTencentCloud from "./forms/AccessConfigFieldsProviderTencentCloud";
import AccessConfigFieldsProviderUCloud from "./forms/AccessConfigFieldsProviderUCloud";
@ -322,15 +323,18 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, on
case ACCESS_PROVIDERS.SPACESHIP: {
return <AccessConfigFieldsProviderSpaceship />;
}
case ACCESS_PROVIDERS.SSLCOM: {
return <AccessConfigFieldsProviderSSLCom />;
}
case ACCESS_PROVIDERS.SSH: {
return <AccessConfigFieldsProviderSSH disabled={disabled} />;
}
case ACCESS_PROVIDERS.TECHNITIUMDNS: {
return <AccessConfigFieldsProviderTechnitiumDNS />;
}
case ACCESS_PROVIDERS.TELEGRAMBOT: {
return <AccessConfigFieldsProviderTelegramBot />;
}
case ACCESS_PROVIDERS.SSLCOM: {
return <AccessConfigFieldsProviderSSLCom />;
}
case ACCESS_PROVIDERS.TENCENTCLOUD: {
return <AccessConfigFieldsProviderTencentCloud />;
}

View File

@ -0,0 +1,76 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Switch } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const AccessConfigFormFieldsProviderTechnitiumDNS = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item
name={[parentNamePath, "serverUrl"]}
initialValue={initialValues.serverUrl}
label={t("access.form.technitiumdns_server_url.label")}
rules={[formRule]}
>
<Input placeholder={t("access.form.technitiumdns_server_url.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "apiToken"]}
initialValue={initialValues.apiToken}
label={t("access.form.technitiumdns_api_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.technitiumdns_api_token.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.technitiumdns_api_token.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "allowInsecureConnections"]}
initialValue={initialValues.allowInsecureConnections}
label={t("access.form.shared_allow_insecure_conns.label")}
rules={[formRule]}
>
<Switch
checkedChildren={t("access.form.shared_allow_insecure_conns.switch.on")}
unCheckedChildren={t("access.form.shared_allow_insecure_conns.switch.off")}
/>
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
serverUrl: "http://<your-host-addr>:5380/",
apiToken: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
serverUrl: z.url(t("common.errmsg.url_invalid")),
apiToken: z.string().nonempty(t("access.form.technitiumdns_api_token.placeholder")),
allowInsecureConnections: z.boolean().nullish(),
});
};
const _default = Object.assign(AccessConfigFormFieldsProviderTechnitiumDNS, {
getInitialValues,
getSchema,
});
export default _default;

View File

@ -83,6 +83,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
SPACESHIP: "spaceship",
SSH: "ssh",
SSLCOM: "sslcom",
TECHNITIUMDNS: "technitiumdns",
TELEGRAMBOT: "telegrambot",
TENCENTCLOUD: "tencentcloud",
UCLOUD: "ucloud",
@ -185,6 +186,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud", "/imgs/providers/cmcccloud.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.POWERDNS, "provider.powerdns", "/imgs/providers/powerdns.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.TECHNITIUMDNS, "provider.technitiumdns", "/imgs/providers/technitiumdns.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.ACMEDNS, "provider.acmedns", "/imgs/providers/acmedns.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.ACMEHTTPREQ, "provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", [ACCESS_USAGES.DNS]],
@ -320,6 +322,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
RAINYUN: `${ACCESS_PROVIDERS.RAINYUN}`,
SPACESHIP: `${ACCESS_PROVIDERS.SPACESHIP}`,
UCLOUD_UDNR: `${ACCESS_PROVIDERS.UCLOUD}-udnr`,
TECHNITIUMDNS: `${ACCESS_PROVIDERS.TECHNITIUMDNS}`,
TENCENTCLOUD: `${ACCESS_PROVIDERS.TENCENTCLOUD}`, // 兼容旧值,等同于 `TENCENTCLOUD_DNS`
TENCENTCLOUD_DNS: `${ACCESS_PROVIDERS.TENCENTCLOUD}-dns`,
TENCENTCLOUD_EO: `${ACCESS_PROVIDERS.TENCENTCLOUD}-eo`,
@ -380,6 +383,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
[ACME_DNS01_PROVIDERS.UCLOUD_UDNR, "provider.ucloud.udnr"],
[ACME_DNS01_PROVIDERS.WESTCN, "provider.westcn"],
[ACME_DNS01_PROVIDERS.POWERDNS, "provider.powerdns"],
[ACME_DNS01_PROVIDERS.TECHNITIUMDNS, "provider.technitiumdns"],
[ACME_DNS01_PROVIDERS.ACMEDNS, "provider.acmedns"],
[ACME_DNS01_PROVIDERS.ACMEHTTPREQ, "provider.acmehttpreq"],
] satisfies Array<[ACMEDns01ProviderType, string]>

View File

@ -122,9 +122,6 @@
"access.form.baiducloud_secret_access_key.label": "Baidu Cloud SecretAccessKey",
"access.form.baiducloud_secret_access_key.placeholder": "Please enter Baidu Cloud SecretAccessKey",
"access.form.baiducloud_secret_access_key.tooltip": "For more information, see <a href=\"https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en\" target=\"_blank\">https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en</a>",
"access.form.bunny_api_key.label": "Bunny API key",
"access.form.bunny_api_key.placeholder": "Please enter Bunny API key",
"access.form.bunny_api_key.tooltip": "For more information, see <a href=\"https://docs.bunny.net/reference/bunnynet-api-overview\" target=\"_blank\">https://docs.bunny.net/reference/bunnynet-api-overview</a>",
"access.form.baishan_api_token.label": "Baishan Cloud API token",
"access.form.baishan_api_token.placeholder": "Please enter Baishan Cloud API token",
"access.form.baotapanel_server_url.label": "aaPanel server URL",
@ -139,6 +136,9 @@
"access.form.baotawaf_api_key.label": "aaWAF API key",
"access.form.baotawaf_api_key.placeholder": "Please enter aaWAF API key",
"access.form.baotawaf_api_key.tooltip": "For more information, see <a href=\"https://github.com/aaPanel/aaWAF/blob/main/API.md\" target=\"_blank\">https://github.com/aaPanel/aaWAF/blob/main/API.md</a>",
"access.form.bunny_api_key.label": "Bunny API key",
"access.form.bunny_api_key.placeholder": "Please enter Bunny API key",
"access.form.bunny_api_key.tooltip": "For more information, see <a href=\"https://docs.bunny.net/reference/bunnynet-api-overview\" target=\"_blank\">https://docs.bunny.net/reference/bunnynet-api-overview</a>",
"access.form.byteplus_access_key.label": "BytePlus AccessKey",
"access.form.byteplus_access_key.placeholder": "Please enter BytePlus AccessKey",
"access.form.byteplus_access_key.tooltip": "For more information, see <a href=\"https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys\" target=\"_blank\">https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys</a>",
@ -454,6 +454,11 @@
"access.form.telegrambot_token.label": "Telegram bot token",
"access.form.telegrambot_token.placeholder": "Please enter Telegram bot token",
"access.form.telegrambot_token.tooltip": "How to get it? Please refer to <a href=\"https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a\" target=\"_blank\">https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a</a>",
"access.form.technitiumdns_server_url.label": "Technitium DNS server URL",
"access.form.technitiumdns_server_url.placeholder": "Please enter Technitium DNS server URL",
"access.form.technitiumdns_api_token.label": "Technitium DNS API token",
"access.form.technitiumdns_api_token.placeholder": "Please enter Technitium DNS API token",
"access.form.technitiumdns_api_token.tooltip": "For more information, see <a href=\"https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md\" target=\"_blank\">https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md</a>",
"access.form.telegrambot_chat_id.label": "Telegram chat ID (Optional)",
"access.form.telegrambot_chat_id.placeholder": "Please enter the default Telegram chat ID",
"access.form.telegrambot_chat_id.help": "Notes: It can be overrided in the workflows.",

View File

@ -138,6 +138,7 @@
"provider.spaceship": "Spaceship",
"provider.ssh": "Remote host (SSH)",
"provider.sslcom": "SSL.com",
"provider.technitiumdns": "Technitium DNS",
"provider.telegrambot": "Telegram Bot",
"provider.tencentcloud": "Tencent Cloud",
"provider.tencentcloud.cdn": "Tencent Cloud - CDN (Content Delivery Network)",

View File

@ -450,6 +450,11 @@
"access.form.ssh_jump_servers.item.label": "跳板机",
"access.form.ssh_jump_servers.add": "添加跳板机",
"access.form.sslcom_eab.guide": "点击下方链接了解如何获取 SSL.com EAB<br><a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
"access.form.technitiumdns_server_url.label": "Technitium DNS 服务地址",
"access.form.technitiumdns_server_url.placeholder": "请输入 Technitium DNS 服务地址",
"access.form.technitiumdns_api_token.label": "Technitium DNS API Token",
"access.form.technitiumdns_api_token.placeholder": "请输入 Technitium DNS API Token",
"access.form.technitiumdns_api_token.tooltip": "这是什么?请参阅 <a href=\"https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md\" target=\"_blank\">https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md</a>",
"access.form.telegrambot_token.label": "Telegram 机器人 API Token",
"access.form.telegrambot_token.placeholder": "请输入 Telegram 机器人 API Token",
"access.form.telegrambot_token.tooltip": "如何获取此参数?请参阅 <a href=\"https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a\" target=\"_blank\">https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a</a>",

View File

@ -138,6 +138,7 @@
"provider.spaceship": "Spaceship",
"provider.ssh": "远程主机SSH",
"provider.sslcom": "SSL.com",
"provider.technitiumdns": "Technitium DNS",
"provider.telegrambot": "Telegram 机器人",
"provider.tencentcloud": "腾讯云",
"provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN",