tencentcloud: fix subdomain error (#1678)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
parent
6a53daad64
commit
c270c239b5
6 changed files with 111 additions and 57 deletions
|
@ -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()
|
||||
|
|
|
@ -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 you@example.com --dns auroradns --domains my.example.org run
|
||||
```
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
||||
func getDomainData(fqdn string) (*domainData, error) {
|
||||
zone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
_, err := d.client.CreateRecord(request)
|
||||
if err != nil {
|
||||
return err
|
||||
var hostedZone *dnspod.DomainListItem
|
||||
for _, zone := range domains {
|
||||
if *zone.Name == dns01.UnFqdn(authZone) {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
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.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
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ package tencentcloud
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ lego --email you@example.com --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"
|
||||
|
|
Loading…
Reference in a new issue