forked from TrueCloudLab/lego
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_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()
|
||||||
|
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue