tencentcloud: fix subdomain error (#1678)

Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
libla 2022-07-17 20:40:24 +08:00 committed by GitHub
parent 6a53daad64
commit c270c239b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 57 deletions

View file

@ -2086,6 +2086,7 @@ func displayDNSHelp(name string) error {
ew.writeln(` - "TENCENTCLOUD_POLLING_INTERVAL": Time between DNS propagation check`) ew.writeln(` - "TENCENTCLOUD_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "TENCENTCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) ew.writeln(` - "TENCENTCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "TENCENTCLOUD_REGION": Region`) ew.writeln(` - "TENCENTCLOUD_REGION": Region`)
ew.writeln(` - "TENCENTCLOUD_SESSION_TOKEN": Access Key token`)
ew.writeln(` - "TENCENTCLOUD_TTL": The TTL of the TXT record used for the DNS challenge`) ew.writeln(` - "TENCENTCLOUD_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln() ew.writeln()

View file

@ -27,7 +27,7 @@ Here is an example bash command using the Aurora DNS provider:
```bash ```bash
AURORA_API_KEY=xxxxx \ AURORA_API_KEY=xxxxx \
AURORA_SECRET_KEY=yyyyyy \ AURORA_SECRET=yyyyyy \
lego --email you@example.com --dns auroradns --domains my.example.org run lego --email you@example.com --dns auroradns --domains my.example.org run
``` ```

View file

@ -53,6 +53,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| `TENCENTCLOUD_POLLING_INTERVAL` | Time between DNS propagation check | | `TENCENTCLOUD_POLLING_INTERVAL` | Time between DNS propagation check |
| `TENCENTCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | | `TENCENTCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `TENCENTCLOUD_REGION` | Region | | `TENCENTCLOUD_REGION` | Region |
| `TENCENTCLOUD_SESSION_TOKEN` | Access Key token |
| `TENCENTCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge | | `TENCENTCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.

View file

@ -1,70 +1,92 @@
package tencentcloud package tencentcloud
import ( import (
"errors"
"fmt"
"strings" "strings"
"github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/challenge/dns01"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
errorsdk "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323" dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
"golang.org/x/net/idna"
) )
type domainData struct { func (d *DNSProvider) getHostedZone(domain string) (*dnspod.DomainListItem, error) {
domain string request := dnspod.NewDescribeDomainListRequest()
subDomain string
}
func getDomainData(fqdn string) (*domainData, error) { var domains []*dnspod.DomainListItem
zone, err := dns01.FindZoneByFqdn(fqdn)
for {
response, err := d.client.DescribeDomainList(request)
if err != nil {
return nil, fmt.Errorf("API call failed: %w", err)
}
domains = append(domains, response.Response.DomainList...)
if uint64(len(domains)) >= *response.Response.DomainCountInfo.AllTotal {
break
}
request.Offset = common.Int64Ptr(int64(len(domains)))
}
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &domainData{ var hostedZone *dnspod.DomainListItem
domain: dns01.UnFqdn(zone), for _, zone := range domains {
subDomain: dns01.UnFqdn(strings.TrimSuffix(fqdn, zone)), if *zone.Name == dns01.UnFqdn(authZone) {
}, nil hostedZone = zone
} }
func (d *DNSProvider) createRecordData(domainData *domainData, value string) error {
request := dnspod.NewCreateRecordRequest()
request.Domain = common.StringPtr(domainData.domain)
request.SubDomain = common.StringPtr(domainData.subDomain)
request.RecordType = common.StringPtr("TXT")
request.RecordLine = common.StringPtr("默认")
request.Value = common.StringPtr(value)
request.TTL = common.Uint64Ptr(uint64(d.config.TTL))
_, err := d.client.CreateRecord(request)
if err != nil {
return err
} }
return nil if hostedZone == nil {
return nil, fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain)
}
return hostedZone, nil
} }
func (d *DNSProvider) listRecordData(domainData *domainData) ([]*dnspod.RecordListItem, error) { func (d *DNSProvider) findTxtRecords(zone *dnspod.DomainListItem, fqdn string) ([]*dnspod.RecordListItem, error) {
recordName, err := extractRecordName(fqdn, *zone.Name)
if err != nil {
return nil, err
}
request := dnspod.NewDescribeRecordListRequest() request := dnspod.NewDescribeRecordListRequest()
request.Domain = common.StringPtr(domainData.domain) request.Domain = zone.Name
request.Subdomain = common.StringPtr(domainData.subDomain) request.DomainId = zone.DomainId
request.Subdomain = common.StringPtr(recordName)
request.RecordType = common.StringPtr("TXT") request.RecordType = common.StringPtr("TXT")
request.RecordLine = common.StringPtr("默认")
response, err := d.client.DescribeRecordList(request) response, err := d.client.DescribeRecordList(request)
if err != nil { if err != nil {
var sdkError *errorsdk.TencentCloudSDKError
if errors.As(err, &sdkError) {
if sdkError.Code == dnspod.RESOURCENOTFOUND_NODATAOFRECORD {
return nil, nil
}
}
return nil, err return nil, err
} }
return response.Response.RecordList, nil return response.Response.RecordList, nil
} }
func (d *DNSProvider) deleteRecordData(domainData *domainData, item *dnspod.RecordListItem) error { func extractRecordName(fqdn, zone string) (string, error) {
request := dnspod.NewDeleteRecordRequest() asciiDomain, err := idna.ToASCII(zone)
request.Domain = common.StringPtr(domainData.domain)
request.RecordId = item.RecordId
_, err := d.client.DeleteRecord(request)
if err != nil { if err != nil {
return err return "", fmt.Errorf("fail to convert punycode: %w", err)
} }
return nil name := dns01.UnFqdn(fqdn)
if idx := strings.Index(name, "."+asciiDomain); idx != -1 {
return name[:idx], nil
}
return name, nil
} }

View file

@ -4,6 +4,7 @@ package tencentcloud
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"time" "time"
"github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/challenge/dns01"
@ -17,9 +18,10 @@ import (
const ( const (
envNamespace = "TENCENTCLOUD_" envNamespace = "TENCENTCLOUD_"
EnvSecretID = envNamespace + "SECRET_ID" EnvSecretID = envNamespace + "SECRET_ID"
EnvSecretKey = envNamespace + "SECRET_KEY" EnvSecretKey = envNamespace + "SECRET_KEY"
EnvRegion = envNamespace + "REGION" EnvRegion = envNamespace + "REGION"
EnvSessionToken = envNamespace + "SESSION_TOKEN"
EnvTTL = envNamespace + "TTL" EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
@ -29,9 +31,10 @@ const (
// Config is used to configure the creation of the DNSProvider. // Config is used to configure the creation of the DNSProvider.
type Config struct { type Config struct {
SecretID string SecretID string
SecretKey string SecretKey string
Region string Region string
SessionToken string
PropagationTimeout time.Duration PropagationTimeout time.Duration
PollingInterval time.Duration PollingInterval time.Duration
@ -45,7 +48,7 @@ func NewDefaultConfig() *Config {
TTL: env.GetOrDefaultInt(EnvTTL, 600), TTL: env.GetOrDefaultInt(EnvTTL, 600),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
} }
} }
@ -67,6 +70,7 @@ func NewDNSProvider() (*DNSProvider, error) {
config.SecretID = values[EnvSecretID] config.SecretID = values[EnvSecretID]
config.SecretKey = values[EnvSecretKey] config.SecretKey = values[EnvSecretKey]
config.Region = env.GetOrDefaultString(EnvRegion, "") config.Region = env.GetOrDefaultString(EnvRegion, "")
config.SessionToken = env.GetOrDefaultString(EnvSessionToken, "")
return NewDNSProviderConfig(config) return NewDNSProviderConfig(config)
} }
@ -77,14 +81,20 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
return nil, errors.New("tencentcloud: the configuration of the DNS provider is nil") return nil, errors.New("tencentcloud: the configuration of the DNS provider is nil")
} }
if config.SecretID == "" || config.SecretKey == "" { var credential *common.Credential
switch {
case config.SecretID != "" && config.SecretKey != "" && config.SessionToken != "":
credential = common.NewTokenCredential(config.SecretID, config.SecretKey, config.SessionToken)
case config.SecretID != "" && config.SecretKey != "":
credential = common.NewCredential(config.SecretID, config.SecretKey)
default:
return nil, errors.New("tencentcloud: credentials missing") return nil, errors.New("tencentcloud: credentials missing")
} }
credential := common.NewCredential(config.SecretID, config.SecretKey)
cpf := profile.NewClientProfile() cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "dnspod.tencentcloudapi.com" cpf.HttpProfile.Endpoint = "dnspod.tencentcloudapi.com"
cpf.HttpProfile.ReqTimeout = int(math.Round(config.HTTPTimeout.Seconds()))
client, err := dnspod.NewClient(credential, config.Region, cpf) client, err := dnspod.NewClient(credential, config.Region, cpf)
if err != nil { if err != nil {
@ -104,14 +114,28 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth) fqdn, value := dns01.GetRecord(domain, keyAuth)
domainData, err := getDomainData(fqdn) zone, err := d.getHostedZone(domain)
if err != nil { if err != nil {
return fmt.Errorf("tencentcloud: failed to get domain data: %w", err) return fmt.Errorf("tencentcloud: failed to get hosted zone: %w", err)
} }
err = d.createRecordData(domainData, value) recordName, err := extractRecordName(fqdn, *zone.Name)
if err != nil { if err != nil {
return fmt.Errorf("tencentcloud: create record failed: %w", err) return fmt.Errorf("tencentcloud: failed to extract record name: %w", err)
}
request := dnspod.NewCreateRecordRequest()
request.Domain = zone.Name
request.DomainId = zone.DomainId
request.SubDomain = common.StringPtr(recordName)
request.RecordType = common.StringPtr("TXT")
request.RecordLine = common.StringPtr("默认")
request.Value = common.StringPtr(value)
request.TTL = common.Uint64Ptr(uint64(d.config.TTL))
_, err = d.client.CreateRecord(request)
if err != nil {
return fmt.Errorf("dnspod: API call failed: %w", err)
} }
return nil return nil
@ -121,18 +145,23 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth) fqdn, _ := dns01.GetRecord(domain, keyAuth)
domainData, err := getDomainData(fqdn) zone, err := d.getHostedZone(domain)
if err != nil { if err != nil {
return fmt.Errorf("tencentcloud: failed to get domain data: %w", err) return fmt.Errorf("tencentcloud: failed to get hosted zone: %w", err)
} }
records, err := d.listRecordData(domainData) records, err := d.findTxtRecords(zone, fqdn)
if err != nil { if err != nil {
return fmt.Errorf("tencentcloud: list records failed: %w", err) return fmt.Errorf("tencentcloud: failed to find TXT records: %w", err)
} }
for _, item := range records { for _, record := range records {
err := d.deleteRecordData(domainData, item) request := dnspod.NewDeleteRecordRequest()
request.Domain = zone.Name
request.DomainId = zone.DomainId
request.RecordId = record.RecordId
_, err := d.client.DeleteRecord(request)
if err != nil { if err != nil {
return fmt.Errorf("tencentcloud: delete record failed: %w", err) return fmt.Errorf("tencentcloud: delete record failed: %w", err)
} }

View file

@ -15,6 +15,7 @@ lego --email you@example.com --dns tencentcloud --domains my.example.org run
TENCENTCLOUD_SECRET_ID = "Access key ID" TENCENTCLOUD_SECRET_ID = "Access key ID"
TENCENTCLOUD_SECRET_KEY = "Access Key secret" TENCENTCLOUD_SECRET_KEY = "Access Key secret"
[Configuration.Additional] [Configuration.Additional]
TENCENTCLOUD_SESSION_TOKEN = "Access Key token"
TENCENTCLOUD_REGION = "Region" TENCENTCLOUD_REGION = "Region"
TENCENTCLOUD_POLLING_INTERVAL = "Time between DNS propagation check" TENCENTCLOUD_POLLING_INTERVAL = "Time between DNS propagation check"
TENCENTCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" TENCENTCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"