Skip to content

Commit

Permalink
tencentcloud: fix subdomain error (#1678)
Browse files Browse the repository at this point in the history
Co-authored-by: Fernandez Ludovic <[email protected]>
  • Loading branch information
libla and ldez authored Jul 17, 2022
1 parent 6a53daa commit c270c23
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 56 deletions.
1 change: 1 addition & 0 deletions cmd/zz_gen_cmd_dnshelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,7 @@ func displayDNSHelp(name string) error {
ew.writeln(` - "TENCENTCLOUD_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "TENCENTCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
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()
Expand Down
2 changes: 1 addition & 1 deletion docs/content/dns/zz_gen_auroradns.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Here is an example bash command using the Aurora DNS provider:

```bash
AURORA_API_KEY=xxxxx \
AURORA_SECRET_KEY=yyyyyy \
AURORA_SECRET=yyyyyy \
lego --email [email protected] --dns auroradns --domains my.example.org run
```

Expand Down
1 change: 1 addition & 0 deletions docs/content/dns/zz_gen_tencentcloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| `TENCENTCLOUD_POLLING_INTERVAL` | Time between DNS propagation check |
| `TENCENTCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `TENCENTCLOUD_REGION` | Region |
| `TENCENTCLOUD_SESSION_TOKEN` | Access Key token |
| `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.
Expand Down
92 changes: 57 additions & 35 deletions providers/dns/tencentcloud/client.go
Original file line number Diff line number Diff line change
@@ -1,70 +1,92 @@
package tencentcloud

import (
"errors"
"fmt"
"strings"

"github.com/go-acme/lego/v4/challenge/dns01"
"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"
"golang.org/x/net/idna"
)

type domainData struct {
domain string
subDomain string
}
func (d *DNSProvider) getHostedZone(domain string) (*dnspod.DomainListItem, error) {
request := dnspod.NewDescribeDomainListRequest()

var domains []*dnspod.DomainListItem

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
}

func getDomainData(fqdn string) (*domainData, error) {
zone, err := dns01.FindZoneByFqdn(fqdn)
request.Offset = common.Int64Ptr(int64(len(domains)))
}

authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
if err != nil {
return nil, err
}

return &domainData{
domain: dns01.UnFqdn(zone),
subDomain: dns01.UnFqdn(strings.TrimSuffix(fqdn, zone)),
}, nil
}

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))
var hostedZone *dnspod.DomainListItem
for _, zone := range domains {
if *zone.Name == dns01.UnFqdn(authZone) {
hostedZone = zone
}
}

_, err := d.client.CreateRecord(request)
if err != nil {
return err
if hostedZone == nil {
return nil, fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain)
}

return nil
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.Domain = common.StringPtr(domainData.domain)
request.Subdomain = common.StringPtr(domainData.subDomain)
request.Domain = zone.Name
request.DomainId = zone.DomainId
request.Subdomain = common.StringPtr(recordName)
request.RecordType = common.StringPtr("TXT")
request.RecordLine = common.StringPtr("默认")

response, err := d.client.DescribeRecordList(request)
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 response.Response.RecordList, nil
}

func (d *DNSProvider) deleteRecordData(domainData *domainData, item *dnspod.RecordListItem) error {
request := dnspod.NewDeleteRecordRequest()
request.Domain = common.StringPtr(domainData.domain)
request.RecordId = item.RecordId

_, err := d.client.DeleteRecord(request)
func extractRecordName(fqdn, zone string) (string, error) {
asciiDomain, err := idna.ToASCII(zone)
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
}
69 changes: 49 additions & 20 deletions providers/dns/tencentcloud/tencentcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package tencentcloud
import (
"errors"
"fmt"
"math"
"time"

"github.com/go-acme/lego/v4/challenge/dns01"
Expand All @@ -17,9 +18,10 @@ import (
const (
envNamespace = "TENCENTCLOUD_"

EnvSecretID = envNamespace + "SECRET_ID"
EnvSecretKey = envNamespace + "SECRET_KEY"
EnvRegion = envNamespace + "REGION"
EnvSecretID = envNamespace + "SECRET_ID"
EnvSecretKey = envNamespace + "SECRET_KEY"
EnvRegion = envNamespace + "REGION"
EnvSessionToken = envNamespace + "SESSION_TOKEN"

EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
Expand All @@ -29,9 +31,10 @@ const (

// Config is used to configure the creation of the DNSProvider.
type Config struct {
SecretID string
SecretKey string
Region string
SecretID string
SecretKey string
Region string
SessionToken string

PropagationTimeout time.Duration
PollingInterval time.Duration
Expand All @@ -45,7 +48,7 @@ func NewDefaultConfig() *Config {
TTL: env.GetOrDefaultInt(EnvTTL, 600),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}

Expand All @@ -67,6 +70,7 @@ func NewDNSProvider() (*DNSProvider, error) {
config.SecretID = values[EnvSecretID]
config.SecretKey = values[EnvSecretKey]
config.Region = env.GetOrDefaultString(EnvRegion, "")
config.SessionToken = env.GetOrDefaultString(EnvSessionToken, "")

return NewDNSProviderConfig(config)
}
Expand All @@ -77,14 +81,20 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
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")
}

credential := common.NewCredential(config.SecretID, config.SecretKey)

cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "dnspod.tencentcloudapi.com"
cpf.HttpProfile.ReqTimeout = int(math.Round(config.HTTPTimeout.Seconds()))

client, err := dnspod.NewClient(credential, config.Region, cpf)
if err != nil {
Expand All @@ -104,14 +114,28 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)

domainData, err := getDomainData(fqdn)
zone, err := d.getHostedZone(domain)
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 {
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
Expand All @@ -121,18 +145,23 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth)

domainData, err := getDomainData(fqdn)
zone, err := d.getHostedZone(domain)
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 {
return fmt.Errorf("tencentcloud: list records failed: %w", err)
return fmt.Errorf("tencentcloud: failed to find TXT records: %w", err)
}

for _, item := range records {
err := d.deleteRecordData(domainData, item)
for _, record := range records {
request := dnspod.NewDeleteRecordRequest()
request.Domain = zone.Name
request.DomainId = zone.DomainId
request.RecordId = record.RecordId

_, err := d.client.DeleteRecord(request)
if err != nil {
return fmt.Errorf("tencentcloud: delete record failed: %w", err)
}
Expand Down
1 change: 1 addition & 0 deletions providers/dns/tencentcloud/tencentcloud.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ lego --email [email protected] --dns tencentcloud --domains my.example.org run
TENCENTCLOUD_SECRET_ID = "Access key ID"
TENCENTCLOUD_SECRET_KEY = "Access Key secret"
[Configuration.Additional]
TENCENTCLOUD_SESSION_TOKEN = "Access Key token"
TENCENTCLOUD_REGION = "Region"
TENCENTCLOUD_POLLING_INTERVAL = "Time between DNS propagation check"
TENCENTCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
Expand Down

0 comments on commit c270c23

Please sign in to comment.