baiducloud.go raw
1 // Package baiducloud implements a DNS provider for solving the DNS-01 challenge using Baidu Cloud.
2 package baiducloud
3
4 import (
5 "errors"
6 "fmt"
7 "time"
8
9 baidudns "github.com/baidubce/bce-sdk-go/services/dns"
10 "github.com/go-acme/lego/v4/challenge/dns01"
11 "github.com/go-acme/lego/v4/platform/config/env"
12 "github.com/go-acme/lego/v4/providers/dns/internal/ptr"
13 )
14
15 // Environment variables names.
16 const (
17 envNamespace = "BAIDUCLOUD_"
18
19 EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID"
20 EnvSecretAccessKey = envNamespace + "SECRET_ACCESS_KEY"
21
22 EnvTTL = envNamespace + "TTL"
23 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
24 EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
25 )
26
27 // 300 is the minimum TTL for free users.
28 const defaultTTL = 300
29
30 // Config is used to configure the creation of the DNSProvider.
31 type Config struct {
32 AccessKeyID string
33 SecretAccessKey string
34
35 PropagationTimeout time.Duration
36 PollingInterval time.Duration
37 TTL int
38 }
39
40 // NewDefaultConfig returns a default configuration for the DNSProvider.
41 func NewDefaultConfig() *Config {
42 return &Config{
43 TTL: env.GetOrDefaultInt(EnvTTL, defaultTTL),
44 PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
45 PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
46 }
47 }
48
49 // DNSProvider implements the challenge.Provider interface.
50 type DNSProvider struct {
51 config *Config
52 client *baidudns.Client
53 }
54
55 // NewDNSProvider returns a DNSProvider instance configured for Baidu Cloud.
56 func NewDNSProvider() (*DNSProvider, error) {
57 values, err := env.Get(EnvAccessKeyID, EnvSecretAccessKey)
58 if err != nil {
59 return nil, fmt.Errorf("baiducloud: %w", err)
60 }
61
62 config := NewDefaultConfig()
63 config.AccessKeyID = values[EnvAccessKeyID]
64 config.SecretAccessKey = values[EnvSecretAccessKey]
65
66 return NewDNSProviderConfig(config)
67 }
68
69 // NewDNSProviderConfig return a DNSProvider instance configured for Baidu Cloud.
70 func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
71 if config == nil {
72 return nil, errors.New("baiducloud: the configuration of the DNS provider is nil")
73 }
74
75 if config.AccessKeyID == "" && config.SecretAccessKey == "" {
76 return nil, errors.New("baiducloud: credentials missing")
77 }
78
79 client, err := baidudns.NewClient(config.AccessKeyID, config.SecretAccessKey, "")
80 if err != nil {
81 return nil, fmt.Errorf("baiducloud: %w", err)
82 }
83
84 return &DNSProvider{
85 config: config,
86 client: client,
87 }, nil
88 }
89
90 // Present creates a TXT record using the specified parameters.
91 func (d *DNSProvider) Present(domain, token, keyAuth string) error {
92 info := dns01.GetChallengeInfo(domain, keyAuth)
93
94 authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
95 if err != nil {
96 return fmt.Errorf("baiducloud: could not find zone for domain %q: %w", domain, err)
97 }
98
99 subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
100 if err != nil {
101 return fmt.Errorf("baiducloud: %w", err)
102 }
103
104 crr := &baidudns.CreateRecordRequest{
105 Description: ptr.Pointer("lego"),
106 Rr: subDomain,
107 Type: "TXT",
108 Value: info.Value,
109 Ttl: ptr.Pointer(int32(d.config.TTL)),
110 }
111
112 err = d.client.CreateRecord(dns01.UnFqdn(authZone), crr, "")
113 if err != nil {
114 return fmt.Errorf("baiducloud: create record: %w", err)
115 }
116
117 return nil
118 }
119
120 // CleanUp removes the TXT record matching the specified parameters.
121 func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
122 info := dns01.GetChallengeInfo(domain, keyAuth)
123
124 authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
125 if err != nil {
126 return fmt.Errorf("baiducloud: could not find zone for domain %q: %w", domain, err)
127 }
128
129 recordID, err := d.findRecordID(dns01.UnFqdn(authZone), info.Value)
130 if err != nil {
131 return fmt.Errorf("baiducloud: find record: %w", err)
132 }
133
134 err = d.client.DeleteRecord(dns01.UnFqdn(authZone), recordID, "")
135 if err != nil {
136 return fmt.Errorf("baiducloud: delete record: %w", err)
137 }
138
139 return nil
140 }
141
142 func (d *DNSProvider) findRecordID(zoneName, tokenValue string) (string, error) {
143 lrr := &baidudns.ListRecordRequest{}
144
145 for {
146 recordResponse, err := d.client.ListRecord(zoneName, lrr)
147 if err != nil {
148 return "", fmt.Errorf("baiducloud: list record: %w", err)
149 }
150
151 for _, record := range recordResponse.Records {
152 if record.Type == "TXT" && record.Value == tokenValue {
153 return record.Id, nil
154 }
155 }
156
157 if !recordResponse.IsTruncated {
158 break
159 }
160
161 lrr.Marker = recordResponse.NextMarker
162 }
163
164 return "", errors.New("record not found")
165 }
166
167 // Timeout returns the timeout and interval to use when checking for DNS propagation.
168 // Adjusting here to cope with spikes in propagation times.
169 func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
170 return d.config.PropagationTimeout, d.config.PollingInterval
171 }
172