allinssl/backend/internal/cert/apply/lego/jdcloud/lego.go

243 lines
6.6 KiB
Go

// This file is originally from Certimate by fudiwei
// Source: https://github.com/usual2970/certimate/blob/main/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/jdcloud.go
// License: MIT
package 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)
client := jddnsclient.NewDomainserviceClient(clientCredentials)
clientConfig := &client.Config
clientConfig.SetTimeout(config.HTTPTimeout)
client.SetConfig(clientConfig)
client.SetLogger(jdcore.NewDefaultLogger(jdcore.LogWarn))
return &DNSProvider{
client: client,
config: config,
}, 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("jdcloud: could not find zone for domain %q: %w", domain, err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("jdcloud: %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("jdcloud: could not find zone for domain %q: %w", domain, err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); 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(zoneName string) (*jddnsmodel.DomainInfo, error) {
pageNumber := 1
pageSize := 10
for {
request := jddnsapi.NewDescribeDomainsRequest(d.config.RegionId, pageNumber, pageSize)
request.SetDomainName(zoneName)
response, err := d.client.DescribeDomains(request)
if err != nil {
return nil, err
}
for _, item := range response.Result.DataList {
if item.DomainName == zoneName {
return &item, nil
}
}
if len(response.Result.DataList) < pageSize {
break
}
pageNumber++
}
return nil, fmt.Errorf("jdcloud: zone %s not found", zoneName)
}
func (d *DNSProvider) getDNSZoneAndRecord(zoneName, subDomain string) (*jddnsmodel.DomainInfo, *jddnsmodel.RRInfo, error) {
zone, err := d.getDNSZone(zoneName)
if err != nil {
return nil, nil, err
}
pageNumber := 1
pageSize := 10
for {
request := jddnsapi.NewDescribeResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id))
request.SetSearch(subDomain)
request.SetPageNumber(pageNumber)
request.SetPageSize(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 zone, nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
zone, record, err := d.getDNSZoneAndRecord(zoneName, 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),
ViewValue: -1,
})
_, 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),
ViewValue: -1,
})
_, err := d.client.ModifyResourceRecord(request)
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
zone, record, err := d.getDNSZoneAndRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
} else {
request := jddnsapi.NewDeleteResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id), fmt.Sprintf("%d", record.Id))
_, err = d.client.DeleteResourceRecord(request)
return err
}
}