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

This commit is contained in:
Fu Diwei
2025-12-05 10:35:23 +08:00
parent d26b9015cd
commit 7f6041fd3d
27 changed files with 2259 additions and 1805 deletions
@@ -42,8 +42,8 @@ type Config struct {
}
type DNSProvider struct {
client *ecloudsdkclouddns.Client
config *Config
client *ecloudsdkclouddns.Client
recordIDs map[string]string
recordIDsMu sync.Mutex
@@ -88,8 +88,8 @@ func NewDNSProviderConfig(cfg *Config) (*DNSProvider, error) {
})
return &DNSProvider{
client: client,
config: cfg,
client: client,
recordIDs: make(map[string]string),
recordIDsMu: sync.Mutex{},
}, nil
@@ -74,7 +74,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := ctyundns.NewClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, err
return nil, fmt.Errorf("ctyun: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
@@ -0,0 +1,37 @@
package dnsexit
import (
"errors"
"time"
"github.com/certimate-go/certimate/pkg/core/certifier"
"github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/dnsexit/internal"
)
type ChallengerConfig struct {
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int `json:"dnsTTL,omitempty"`
}
func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) {
if config == nil {
return nil, errors.New("the configuration of the acme challenge provider is nil")
}
providerConfig := internal.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = config.DnsTTL
}
provider, err := internal.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}
@@ -0,0 +1,143 @@
package internal
import (
"errors"
"fmt"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/samber/lo"
dnsexitsdk "github.com/certimate-go/certimate/pkg/sdk3rd/dnsexit"
)
const (
envNamespace = "DNSEXIT_"
EnvAPIKey = envNamespace + "APIKEY"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
APIKey string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
HTTPTimeout time.Duration
}
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, 0),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}
type DNSProvider struct {
config *Config
client *dnsexitsdk.Client
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAPIKey)
if err != nil {
return nil, fmt.Errorf("dnsexit: %w", err)
}
config := NewDefaultConfig()
config.APIKey = values[EnvAPIKey]
return NewDNSProviderConfig(config)
}
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("dnsexit: the configuration of the DNS provider is nil")
}
client, err := dnsexitsdk.NewClient(config.APIKey)
if err != nil {
return nil, fmt.Errorf("dnsexit: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
config: config,
client: client,
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("dnsexit: could not find zone for domain %q: %w", domain, err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("dnsexit: %w", err)
}
// REF: https://dnsexit.com/dns/dns-api/#example-add-new
request := &dnsexitsdk.DnsRecordRequest{
Domain: lo.ToPtr(dns01.UnFqdn(authZone)),
Add: &dnsexitsdk.DnsRecord{
Type: lo.ToPtr("TXT"),
Name: lo.ToPtr(subDomain),
Content: lo.ToPtr(info.Value),
TTL: lo.ToPtr(d.config.TTL),
Overwrite: lo.ToPtr(true),
},
}
if _, err := d.client.DnsRecord(request); err != nil {
return fmt.Errorf("dnsexit: error when create record: %w", err)
}
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("dnsexit: could not find zone for domain %q: %w", domain, err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("dnsexit: %w", err)
}
// REF: https://dnsexit.com/dns/dns-api/#delete-a-record
request := &dnsexitsdk.DnsRecordRequest{
Domain: lo.ToPtr(dns01.UnFqdn(authZone)),
Delete: &dnsexitsdk.DnsRecord{
Type: lo.ToPtr("TXT"),
Name: lo.ToPtr(subDomain),
},
}
if _, err := d.client.DnsRecord(request); err != nil {
return fmt.Errorf("dnsexit: error when delete record: %w", err)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
@@ -40,8 +40,8 @@ type Config struct {
}
type DNSProvider struct {
client *dnslasdk.Client
config *Config
client *dnslasdk.Client
recordIDs map[string]string
recordIDsMu sync.Mutex
@@ -76,14 +76,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := dnslasdk.NewClient(config.APIId, config.APISecret)
if err != nil {
return nil, err
return nil, fmt.Errorf("dnsla: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]string),
recordIDsMu: sync.Mutex{},
}, nil
@@ -34,8 +34,8 @@ type Config struct {
}
type DNSProvider struct {
client *dynv6.Provider
config *Config
client *dynv6.Provider
}
func NewDefaultConfig() *Config {
@@ -66,8 +66,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client := &dynv6.Provider{Token: config.HTTPToken}
return &DNSProvider{
client: client,
config: config,
client: client,
}, nil
}
@@ -39,8 +39,8 @@ type Config struct {
}
type DNSProvider struct {
client *gnamesdk.Client
config *Config
client *gnamesdk.Client
recordIDs map[string]int64
recordIDsMu sync.Mutex
@@ -75,14 +75,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := gnamesdk.NewClient(config.AppID, config.AppKey)
if err != nil {
return nil, err
return nil, fmt.Errorf("gname: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]int64),
recordIDsMu: sync.Mutex{},
}, nil
@@ -41,8 +41,8 @@ type Config struct {
}
type DNSProvider struct {
client *DomainserviceClient
config *Config
client *DomainserviceClient
recordIDs map[string]int
recordIDsMu sync.Mutex
@@ -81,8 +81,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client.Config.SetTimeout(config.HTTPTimeout)
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]int),
recordIDsMu: sync.Mutex{},
}, nil
@@ -39,8 +39,8 @@ type Config struct {
}
type DNSProvider struct {
client *qingcloudsdk.Client
config *Config
client *qingcloudsdk.Client
recordIDs map[string]*int64
recordIDsMu sync.Mutex
@@ -75,14 +75,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := qingcloudsdk.NewClient(config.AccessKey, config.AccessSecret)
if err != nil {
return nil, err
return nil, fmt.Errorf("qingcloud: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]*int64),
recordIDsMu: sync.Mutex{},
}, nil
@@ -41,8 +41,8 @@ type Config struct {
}
type DNSProvider struct {
client *udnr.UDNRClient
config *Config
client *udnr.UDNRClient
}
func NewDefaultConfig() *Config {
@@ -81,8 +81,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client := udnr.NewClient(&cfg, &credential)
return &DNSProvider{
client: client,
config: config,
client: client,
}, nil
}
@@ -39,8 +39,8 @@ type Config struct {
}
type DNSProvider struct {
client *xinnetsdk.Client
config *Config
client *xinnetsdk.Client
recordIDs map[string]*int64
recordIDsMu sync.Mutex
@@ -75,14 +75,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := xinnetsdk.NewClient(config.AgentID, config.AppSecret)
if err != nil {
return nil, err
return nil, fmt.Errorf("xinnet: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]*int64),
recordIDsMu: sync.Mutex{},
}, nil
@@ -196,8 +196,9 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
privkey, err := xcert.ParsePrivateKeyFromPEM(privkeyPEM)
if err != nil {
return nil, fmt.Errorf("could not parse private key: %w", err)
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
privkeyAlg, _, _ := xcryptokey.GetPrivateKeyAlgorithm(privkey)
privkeyAlgStr := ""
switch privkeyAlg {