checkdomain.go raw
1 // Package checkdomain implements a DNS provider for solving the DNS-01 challenge using CheckDomain DNS.
2 package checkdomain
3
4 import (
5 "context"
6 "errors"
7 "fmt"
8 "net/http"
9 "net/url"
10 "time"
11
12 "github.com/go-acme/lego/v4/challenge"
13 "github.com/go-acme/lego/v4/challenge/dns01"
14 "github.com/go-acme/lego/v4/platform/config/env"
15 "github.com/go-acme/lego/v4/providers/dns/checkdomain/internal"
16 "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug"
17 )
18
19 // Environment variables names.
20 const (
21 envNamespace = "CHECKDOMAIN_"
22
23 EnvEndpoint = envNamespace + "ENDPOINT"
24 EnvToken = envNamespace + "TOKEN"
25
26 EnvTTL = envNamespace + "TTL"
27 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
28 EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
29 EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
30 )
31
32 var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
33
34 // Config is used to configure the creation of the DNSProvider.
35 type Config struct {
36 Endpoint *url.URL
37 Token string
38 TTL int
39 PropagationTimeout time.Duration
40 PollingInterval time.Duration
41 HTTPClient *http.Client
42 }
43
44 // NewDefaultConfig returns a default configuration for the DNSProvider.
45 func NewDefaultConfig() *Config {
46 return &Config{
47 TTL: env.GetOrDefaultInt(EnvTTL, 300),
48 PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute),
49 PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 7*time.Second),
50 HTTPClient: &http.Client{
51 Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
52 },
53 }
54 }
55
56 // DNSProvider implements the challenge.Provider interface.
57 type DNSProvider struct {
58 config *Config
59 client *internal.Client
60 }
61
62 // NewDNSProvider returns a DNSProvider instance configured for CheckDomain.
63 func NewDNSProvider() (*DNSProvider, error) {
64 values, err := env.Get(EnvToken)
65 if err != nil {
66 return nil, fmt.Errorf("checkdomain: %w", err)
67 }
68
69 config := NewDefaultConfig()
70 config.Token = values[EnvToken]
71
72 endpoint, err := url.Parse(env.GetOrDefaultString(EnvEndpoint, internal.DefaultEndpoint))
73 if err != nil {
74 return nil, fmt.Errorf("checkdomain: invalid %s: %w", EnvEndpoint, err)
75 }
76
77 config.Endpoint = endpoint
78
79 return NewDNSProviderConfig(config)
80 }
81
82 func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
83 if config.Endpoint == nil {
84 return nil, errors.New("checkdomain: invalid endpoint")
85 }
86
87 if config.Token == "" {
88 return nil, errors.New("checkdomain: missing token")
89 }
90
91 client := internal.NewClient(
92 clientdebug.Wrap(
93 internal.OAuthStaticAccessToken(config.HTTPClient, config.Token),
94 ),
95 )
96
97 if config.Endpoint != nil {
98 client.BaseURL = config.Endpoint
99 }
100
101 return &DNSProvider{config: config, client: client}, nil
102 }
103
104 // Present creates a TXT record to fulfill the dns-01 challenge.
105 func (d *DNSProvider) Present(domain, token, keyAuth string) error {
106 ctx := context.Background()
107
108 // TODO(ldez) replace domain by FQDN to follow CNAME.
109 domainID, err := d.client.GetDomainIDByName(ctx, domain)
110 if err != nil {
111 return fmt.Errorf("checkdomain: %w", err)
112 }
113
114 err = d.client.CheckNameservers(ctx, domainID)
115 if err != nil {
116 return fmt.Errorf("checkdomain: %w", err)
117 }
118
119 info := dns01.GetChallengeInfo(domain, keyAuth)
120
121 err = d.client.CreateRecord(ctx, domainID, &internal.Record{
122 Name: info.EffectiveFQDN,
123 TTL: d.config.TTL,
124 Type: "TXT",
125 Value: info.Value,
126 })
127 if err != nil {
128 return fmt.Errorf("checkdomain: %w", err)
129 }
130
131 return nil
132 }
133
134 // CleanUp removes the TXT record previously created.
135 func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
136 ctx := context.Background()
137
138 // TODO(ldez) replace domain by FQDN to follow CNAME.
139 domainID, err := d.client.GetDomainIDByName(ctx, domain)
140 if err != nil {
141 return fmt.Errorf("checkdomain: %w", err)
142 }
143
144 err = d.client.CheckNameservers(ctx, domainID)
145 if err != nil {
146 return fmt.Errorf("checkdomain: %w", err)
147 }
148
149 info := dns01.GetChallengeInfo(domain, keyAuth)
150
151 defer d.client.CleanCache(info.EffectiveFQDN)
152
153 err = d.client.DeleteTXTRecord(ctx, domainID, info.EffectiveFQDN, info.Value)
154 if err != nil {
155 return fmt.Errorf("checkdomain: %w", err)
156 }
157
158 return nil
159 }
160
161 // Timeout returns the timeout and interval to use when checking for DNS propagation.
162 // Adjusting here to cope with spikes in propagation times.
163 func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
164 return d.config.PropagationTimeout, d.config.PollingInterval
165 }
166