feat: add jdcloud dns-01 applicant

This commit is contained in:
Fu Diwei
2025-02-19 23:57:22 +08:00
parent 469c24751e
commit 72896e052c
24 changed files with 502 additions and 15 deletions

View File

@@ -184,10 +184,10 @@ func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error {
if record == nil {
return nil
} else {
err = d.client.DeleteRecord(domain, record.Id, d.generateClientToken())
return err
}
err = d.client.DeleteRecord(domain, record.Id, d.generateClientToken())
return err
}
func (d *DNSProvider) generateClientToken() string {

View File

@@ -0,0 +1,229 @@
package lego_jdcloud
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"
jdCore "github.com/jdcloud-api/jdcloud-sdk-go/core"
jdDnsApi "github.com/jdcloud-api/jdcloud-sdk-go/services/domainservice/apis"
jdDnsClient "github.com/jdcloud-api/jdcloud-sdk-go/services/domainservice/client"
jdDnsModel "github.com/jdcloud-api/jdcloud-sdk-go/services/domainservice/models"
)
const (
envNamespace = "JDCLOUD_"
EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID"
EnvAccessKeySecret = envNamespace + "ACCESS_KEY_SECRET"
EnvRegionId = envNamespace + "REGION_ID"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
AccessKeyID string
AccessKeySecret string
RegionId string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int32
HTTPTimeout time.Duration
}
type DNSProvider struct {
client *jdDnsClient.DomainserviceClient
config *Config
}
func NewDefaultConfig() *Config {
return &Config{
TTL: int32(env.GetOrDefaultInt(EnvTTL, 300)),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAccessKeyID, EnvAccessKeySecret)
if err != nil {
return nil, fmt.Errorf("jdcloud: %w", err)
}
config := NewDefaultConfig()
config.AccessKeyID = values[EnvAccessKeyID]
config.AccessKeySecret = values[EnvAccessKeySecret]
config.RegionId = values[EnvRegionId]
return NewDNSProviderConfig(config)
}
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("jdcloud: the configuration of the DNS provider is nil")
}
clientCredentials := jdCore.NewCredentials(config.AccessKeyID, config.AccessKeySecret)
clientConfig := jdCore.NewConfig()
clientConfig.SetTimeout(config.HTTPTimeout)
client := jdDnsClient.NewDomainserviceClient(clientCredentials)
client.SetConfig(clientConfig)
return &DNSProvider{
client: client,
config: config,
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
zoneName, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zoneName)
if err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
if err := d.addOrUpdateDNSRecord(domain, subDomain, info.Value); err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
subDomain := dns01.UnFqdn(fqdn)
if err := d.removeDNSRecord(domain, subDomain, value); err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) getDNSZone(domain string) (*jdDnsModel.DomainInfo, error) {
pageNumber := 1
pageSize := 100
for {
request := &jdDnsApi.DescribeDomainsRequest{}
request.RegionId = d.config.RegionId
request.DomainName = &domain
request.PageNumber = pageNumber
request.PageSize = pageSize
response, err := d.client.DescribeDomains(request)
if err != nil {
return nil, err
}
for _, item := range response.Result.DataList {
if item.DomainName == domain {
return &item, nil
}
}
if len(response.Result.DataList) < pageSize {
break
}
pageNumber++
}
return nil, fmt.Errorf("jdcloud: zone %s not found", domain)
}
func (d *DNSProvider) getDNSZoneAndRecord(domain, subDomain string) (*jdDnsModel.DomainInfo, *jdDnsModel.RRInfo, error) {
zone, err := d.getDNSZone(domain)
if err != nil {
return nil, nil, err
}
pageNumber := 1
pageSize := 100
for {
request := jdDnsApi.NewDescribeResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", &zone.Id))
request.Search = &subDomain
request.PageNumber = &pageNumber
request.PageSize = &pageSize
response, err := d.client.DescribeResourceRecord(request)
if err != nil {
return zone, nil, err
}
for _, record := range response.Result.DataList {
if record.Type == "TXT" && record.HostRecord == subDomain {
return zone, &record, nil
}
}
if len(response.Result.DataList) < pageSize {
break
}
pageNumber++
}
return nil, nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(domain, subDomain, value string) error {
zone, record, err := d.getDNSZoneAndRecord(domain, subDomain)
if err != nil {
return err
}
if record == nil {
request := jdDnsApi.NewCreateResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", &zone.Id), &jdDnsModel.AddRR{
Type: "TXT",
HostRecord: subDomain,
HostValue: value,
Ttl: int(d.config.TTL),
})
_, err := d.client.CreateResourceRecord(request)
return err
} else {
request := jdDnsApi.NewModifyResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", &zone.Id), fmt.Sprintf("%d", &record.Id), &jdDnsModel.UpdateRR{
Type: "TXT",
HostRecord: subDomain,
HostValue: value,
Ttl: int(d.config.TTL),
})
_, err := d.client.ModifyResourceRecord(request)
return err
}
}
func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error {
zone, record, err := d.getDNSZoneAndRecord(domain, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
} else {
req := jdDnsApi.NewDeleteResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", &zone.Id), fmt.Sprintf("%d", &record.Id))
_, err = d.client.DeleteResourceRecord(req)
return err
}
}

View File

@@ -0,0 +1,47 @@
package jdcloud
import (
"time"
"github.com/go-acme/lego/v4/challenge"
internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal"
)
type ChallengeProviderConfig struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
RegionId string `json:"regionId"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
regionId := config.RegionId
if regionId == "" {
// 京东云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
regionId = "cn-north-1"
}
providerConfig := internal.NewDefaultConfig()
providerConfig.AccessKeyID = config.AccessKeyId
providerConfig.AccessKeySecret = config.AccessKeySecret
providerConfig.RegionId = regionId
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
}