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 00000000..90f5c629
Binary files /dev/null and b/ui/public/imgs/providers/rfc.png differ
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