Add DNS Provider for Tencent Cloud (#1527)

This commit is contained in:
Baran 2021-12-22 23:58:35 +08:00 committed by GitHub
parent 0324783e09
commit 6907a39266
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 489 additions and 3 deletions

View file

@ -68,9 +68,9 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
| [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [reg.ru](https://go-acme.github.io/lego/dns/regru/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) |
| [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) |
| [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Simply.com](https://go-acme.github.io/lego/dns/simply/) | [Sonic](https://go-acme.github.io/lego/dns/sonic/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) |
| [TransIP](https://go-acme.github.io/lego/dns/transip/) | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) |
| [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) |
| [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | |
| [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) |
| [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) |
| [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) |
<!-- END DNS PROVIDERS LIST -->

View file

@ -102,6 +102,7 @@ func allDNSCodes() string {
"simply",
"sonic",
"stackpath",
"tencentcloud",
"transip",
"vegadns",
"versio",
@ -1997,6 +1998,28 @@ func displayDNSHelp(name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/stackpath`)
case "tencentcloud":
// generated from: providers/dns/tencentcloud/tencentcloud.toml
ew.writeln(`Configuration for Tencent Cloud DNS.`)
ew.writeln(`Code: 'tencentcloud'`)
ew.writeln(`Since: 'v4.6.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "TENCENTCLOUD_SECRET_ID": Access key ID`)
ew.writeln(` - "TENCENTCLOUD_SECRET_KEY": Access Key secret`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "TENCENTCLOUD_HTTP_TIMEOUT": API request timeout`)
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_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/tencentcloud`)
case "transip":
// generated from: providers/dns/transip/transip.toml
ew.writeln(`Configuration for TransIP.`)

View file

@ -0,0 +1,66 @@
---
title: "Tencent Cloud DNS"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: tencentcloud
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/tencentcloud/tencentcloud.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Since: v4.6.0
Configuration for [Tencent Cloud DNS](https://cloud.tencent.com/product/cns).
<!--more-->
- Code: `tencentcloud`
Here is an example bash command using the Tencent Cloud DNS provider:
```bash
TENCENTCLOUD_SECRET_ID=abcdefghijklmnopqrstuvwx \
TENCENTCLOUD_SECRET_KEY=your-secret-key \
lego --email myemail@example.com --dns tencentcloud --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `TENCENTCLOUD_SECRET_ID` | Access key ID |
| `TENCENTCLOUD_SECRET_KEY` | Access Key secret |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here](/lego/dns/#configuration-and-credentials).
## Additional Configuration
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `TENCENTCLOUD_HTTP_TIMEOUT` | API request timeout |
| `TENCENTCLOUD_POLLING_INTERVAL` | Time between DNS propagation check |
| `TENCENTCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `TENCENTCLOUD_REGION` | Region |
| `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.
More information [here](/lego/dns/#configuration-and-credentials).
## More information
- [API documentation](https://cloud.tencent.com/document/product/1427/56153)
- [Go client](https://github.com/tencentcloud/tencentcloud-sdk-go)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/tencentcloud/tencentcloud.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

2
go.mod
View file

@ -49,6 +49,8 @@ require (
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f
github.com/softlayer/softlayer-go v1.0.3
github.com/stretchr/testify v1.7.0
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.287
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.287
github.com/transip/gotransip/v6 v6.6.1
github.com/urfave/cli v1.22.5
github.com/vinyldns/go-vinyldns v0.9.16

4
go.sum
View file

@ -465,6 +465,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.287 h1:ohsyW4WffPdd2JLPio2Sd0qGr93hzkawAt9vWdCFLgY=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.287/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.287 h1:O/ycBVvdOAmwFlXm0fCtLz2WOr1EaWZQTDM/4pmVT+s=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.287/go.mod h1:CuOaLxOQr477GhMWAQPYQFUJrsZbW+ZqkAgP2uHDZXg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/transip/gotransip/v6 v6.6.1 h1:nsCU1ErZS5G0FeOpgGXc4FsWvBff9GPswSMggsC4564=
github.com/transip/gotransip/v6 v6.6.1/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=

View file

@ -93,6 +93,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/simply"
"github.com/go-acme/lego/v4/providers/dns/sonic"
"github.com/go-acme/lego/v4/providers/dns/stackpath"
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
"github.com/go-acme/lego/v4/providers/dns/transip"
"github.com/go-acme/lego/v4/providers/dns/vegadns"
"github.com/go-acme/lego/v4/providers/dns/versio"
@ -286,6 +287,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return sonic.NewDNSProvider()
case "stackpath":
return stackpath.NewDNSProvider()
case "tencentcloud":
return tencentcloud.NewDNSProvider()
case "transip":
return transip.NewDNSProvider()
case "vegadns":

View file

@ -0,0 +1,70 @@
package tencentcloud
import (
"strings"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
)
type domainData struct {
domain string
subDomain string
}
func getDomainData(fqdn string) (*domainData, error) {
zone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return nil, err
}
return &domainData{
domain: 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
}
return nil
}
func (d *DNSProvider) listRecordData(domainData *domainData) ([]*dnspod.RecordListItem, error) {
request := dnspod.NewDescribeRecordListRequest()
request.Domain = common.StringPtr(domainData.domain)
request.Subdomain = common.StringPtr(domainData.subDomain)
request.RecordType = common.StringPtr("TXT")
response, err := d.client.DescribeRecordList(request)
if err != 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)
if err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,142 @@
// Package tencentcloud implements a DNS provider for solving the DNS-01 challenge using Tencent Cloud DNS.
package tencentcloud
import (
"errors"
"fmt"
"time"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
)
// Environment variables names.
const (
envNamespace = "TENCENTCLOUD_"
EnvSecretID = envNamespace + "SECRET_ID"
EnvSecretKey = envNamespace + "SECRET_KEY"
EnvRegion = envNamespace + "REGION"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
// Config is used to configure the creation of the DNSProvider.
type Config struct {
SecretID string
SecretKey string
Region string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
HTTPTimeout time.Duration
}
// NewDefaultConfig returns a default configuration for the DNSProvider.
func NewDefaultConfig() *Config {
return &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),
}
}
// DNSProvider implements the challenge.Provider interface.
type DNSProvider struct {
config *Config
client *dnspod.Client
}
// NewDNSProvider returns a DNSProvider instance configured for Tencent Cloud DNS.
// Credentials must be passed in the environment variable: TENCENTCLOUD_SECRET_ID, TENCENTCLOUD_SECRET_KEY.
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvSecretID, EnvSecretKey)
if err != nil {
return nil, fmt.Errorf("tencentcloud: %w", err)
}
config := NewDefaultConfig()
config.SecretID = values[EnvSecretID]
config.SecretKey = values[EnvSecretKey]
config.Region = env.GetOrDefaultString(EnvRegion, "")
return NewDNSProviderConfig(config)
}
// NewDNSProviderConfig return a DNSProvider instance configured for Tencent Cloud DNS.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("tencentcloud: the configuration of the DNS provider is nil")
}
if config.SecretID == "" || config.SecretKey == "" {
return nil, errors.New("tencentcloud: credentials missing")
}
credential := common.NewCredential(config.SecretID, config.SecretKey)
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "dnspod.tencentcloudapi.com"
client, err := dnspod.NewClient(credential, config.Region, cpf)
if err != nil {
return nil, fmt.Errorf("tencentcloud: %w", err)
}
return &DNSProvider{config: config, client: client}, nil
}
// Timeout returns the timeout and interval to use when checking for DNS propagation.
// Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
// Present creates a TXT record to fulfill the dns-01 challenge.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
domainData, err := getDomainData(fqdn)
if err != nil {
return fmt.Errorf("tencentcloud: failed to get domain data: %w", err)
}
err = d.createRecordData(domainData, value)
if err != nil {
return fmt.Errorf("tencentcloud: create record failed: %w", err)
}
return nil
}
// CleanUp removes the TXT record matching the specified parameters.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth)
domainData, err := getDomainData(fqdn)
if err != nil {
return fmt.Errorf("tencentcloud: failed to get domain data: %w", err)
}
records, err := d.listRecordData(domainData)
if err != nil {
return fmt.Errorf("tencentcloud: list records failed: %w", err)
}
for _, item := range records {
err := d.deleteRecordData(domainData, item)
if err != nil {
return fmt.Errorf("tencentcloud: delete record failed: %w", err)
}
}
return nil
}

View file

@ -0,0 +1,26 @@
Name = "Tencent Cloud DNS"
Description = ''''''
URL = "https://cloud.tencent.com/product/cns"
Code = "tencentcloud"
Since = "v4.6.0"
Example = '''
TENCENTCLOUD_SECRET_ID=abcdefghijklmnopqrstuvwx \
TENCENTCLOUD_SECRET_KEY=your-secret-key \
lego --email myemail@example.com --dns tencentcloud --domains my.example.org run
'''
[Configuration]
[Configuration.Credentials]
TENCENTCLOUD_SECRET_ID = "Access key ID"
TENCENTCLOUD_SECRET_KEY = "Access Key secret"
[Configuration.Additional]
TENCENTCLOUD_REGION = "Region"
TENCENTCLOUD_POLLING_INTERVAL = "Time between DNS propagation check"
TENCENTCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
TENCENTCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge"
TENCENTCLOUD_HTTP_TIMEOUT = "API request timeout"
[Links]
API = "https://cloud.tencent.com/document/product/1427/56153"
GoClient = "https://github.com/tencentcloud/tencentcloud-sdk-go"

View file

@ -0,0 +1,150 @@
package tencentcloud
import (
"testing"
"time"
"github.com/go-acme/lego/v4/platform/tester"
"github.com/stretchr/testify/require"
)
const envDomain = envNamespace + "DOMAIN"
var envTest = tester.NewEnvTest(EnvSecretID, EnvSecretKey).
WithDomain(envDomain)
func TestNewDNSProvider(t *testing.T) {
testCases := []struct {
desc string
envVars map[string]string
expected string
}{
{
desc: "success",
envVars: map[string]string{
EnvSecretID: "123",
EnvSecretKey: "456",
},
},
{
desc: "missing credentials",
envVars: map[string]string{
EnvSecretID: "",
EnvSecretKey: "",
},
expected: "tencentcloud: some credentials information are missing: TENCENTCLOUD_SECRET_ID,TENCENTCLOUD_SECRET_KEY",
},
{
desc: "missing access id",
envVars: map[string]string{
EnvSecretID: "",
EnvSecretKey: "456",
},
expected: "tencentcloud: some credentials information are missing: TENCENTCLOUD_SECRET_ID",
},
{
desc: "missing secret key",
envVars: map[string]string{
EnvSecretID: "123",
EnvSecretKey: "",
},
expected: "tencentcloud: some credentials information are missing: TENCENTCLOUD_SECRET_KEY",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
defer envTest.RestoreEnv()
envTest.ClearEnv()
envTest.Apply(test.envVars)
p, err := NewDNSProvider()
if test.expected == "" {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
require.NotNil(t, p.client)
} else {
require.EqualError(t, err, test.expected)
}
})
}
}
func TestNewDNSProviderConfig(t *testing.T) {
testCases := []struct {
desc string
secretID string
secretKey string
expected string
}{
{
desc: "success",
secretID: "123",
secretKey: "456",
},
{
desc: "missing credentials",
expected: "tencentcloud: credentials missing",
},
{
desc: "missing secret id",
secretKey: "456",
expected: "tencentcloud: credentials missing",
},
{
desc: "missing secret key",
secretID: "123",
expected: "tencentcloud: credentials missing",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
config := NewDefaultConfig()
config.SecretID = test.secretID
config.SecretKey = test.secretKey
p, err := NewDNSProviderConfig(config)
if test.expected == "" {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
require.NotNil(t, p.client)
} else {
require.EqualError(t, err, test.expected)
}
})
}
}
func TestLivePresent(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")
}
envTest.RestoreEnv()
provider, err := NewDNSProvider()
require.NoError(t, err)
err = provider.Present(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
}
func TestLiveCleanUp(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")
}
envTest.RestoreEnv()
provider, err := NewDNSProvider()
require.NoError(t, err)
time.Sleep(1 * time.Second)
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
}