dnshomede.go raw
1 // Package dnshomede implements a DNS provider for solving the DNS-01 challenge using dnsHome.de.
2 package dnshomede
3
4 import (
5 "context"
6 "errors"
7 "fmt"
8 "net/http"
9 "time"
10
11 "github.com/go-acme/lego/v4/challenge/dns01"
12 "github.com/go-acme/lego/v4/platform/config/env"
13 "github.com/go-acme/lego/v4/providers/dns/dnshomede/internal"
14 "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug"
15 )
16
17 // Environment variables names.
18 const (
19 envNamespace = "DNSHOMEDE_"
20
21 EnvCredentials = envNamespace + "CREDENTIALS"
22
23 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
24 EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
25 EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
26 EnvSequenceInterval = envNamespace + "SEQUENCE_INTERVAL"
27 )
28
29 // Config is used to configure the creation of the DNSProvider.
30 type Config struct {
31 Credentials map[string]string
32 PropagationTimeout time.Duration
33 PollingInterval time.Duration
34 SequenceInterval time.Duration
35 HTTPClient *http.Client
36 }
37
38 // NewDefaultConfig returns a default configuration for the DNSProvider.
39 func NewDefaultConfig() *Config {
40 return &Config{
41 PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 20*time.Minute),
42 PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
43 SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, 2*time.Minute),
44 HTTPClient: &http.Client{
45 Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
46 },
47 }
48 }
49
50 // DNSProvider implements the challenge.Provider interface.
51 type DNSProvider struct {
52 config *Config
53 client *internal.Client
54 }
55
56 // NewDNSProvider returns a DNSProvider instance configured for dnsHome.de.
57 // Credentials must be passed in the environment variable: DNSHOMEDE_CREDENTIALS.
58 func NewDNSProvider() (*DNSProvider, error) {
59 config := NewDefaultConfig()
60
61 values, err := env.Get(EnvCredentials)
62 if err != nil {
63 return nil, fmt.Errorf("dnshomede: %w", err)
64 }
65
66 credentials, err := env.ParsePairs(values[EnvCredentials])
67 if err != nil {
68 return nil, fmt.Errorf("dnshomede: credentials: %w", err)
69 }
70
71 config.Credentials = credentials
72
73 return NewDNSProviderConfig(config)
74 }
75
76 func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
77 if config == nil {
78 return nil, errors.New("dnshomede: the configuration of the DNS provider is nil")
79 }
80
81 if len(config.Credentials) == 0 {
82 return nil, errors.New("dnshomede: missing credentials")
83 }
84
85 for domain, password := range config.Credentials {
86 if domain == "" {
87 return nil, fmt.Errorf(`dnshomede: missing domain: "%s:%s"`, domain, password)
88 }
89
90 if password == "" {
91 return nil, fmt.Errorf(`dnshomede: missing password: "%s:%s"`, domain, password)
92 }
93 }
94
95 client := internal.NewClient(config.Credentials)
96
97 if config.HTTPClient != nil {
98 client.HTTPClient = config.HTTPClient
99 }
100
101 client.HTTPClient = clientdebug.Wrap(client.HTTPClient)
102
103 return &DNSProvider{config: config, client: client}, nil
104 }
105
106 // Present updates a TXT record to fulfill the dns-01 challenge.
107 func (d *DNSProvider) Present(domain, _, keyAuth string) error {
108 info := dns01.GetChallengeInfo(domain, keyAuth)
109
110 err := d.client.Add(context.Background(), dns01.UnFqdn(info.EffectiveFQDN), info.Value)
111 if err != nil {
112 return fmt.Errorf("dnshomede: %w", err)
113 }
114
115 return nil
116 }
117
118 // CleanUp updates the TXT record matching the specified parameters.
119 func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error {
120 info := dns01.GetChallengeInfo(domain, keyAuth)
121
122 err := d.client.Remove(context.Background(), dns01.UnFqdn(info.EffectiveFQDN), info.Value)
123 if err != nil {
124 return fmt.Errorf("dnshomede: %w", err)
125 }
126
127 return nil
128 }
129
130 // Timeout returns the timeout and interval to use when checking for DNS propagation.
131 // Adjusting here to cope with spikes in propagation times.
132 func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
133 return d.config.PropagationTimeout, d.config.PollingInterval
134 }
135
136 // Sequential All DNS challenges for this provider will be resolved sequentially.
137 // Returns the interval between each iteration.
138 func (d *DNSProvider) Sequential() time.Duration {
139 return d.config.SequenceInterval
140 }
141