bindman.go raw
1 // Package bindman implements a DNS provider for solving the DNS-01 challenge.
2 package bindman
3
4 import (
5 "errors"
6 "fmt"
7 "net/http"
8 "time"
9
10 "github.com/go-acme/lego/v4/challenge"
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/internal/clientdebug"
14 bindman "github.com/labbsr0x/bindman-dns-webhook/src/client"
15 )
16
17 // Environment variables names.
18 const (
19 envNamespace = "BINDMAN_"
20
21 EnvManagerAddress = envNamespace + "MANAGER_ADDRESS"
22
23 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
24 EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
25 EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
26 )
27
28 var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
29
30 // Config is used to configure the creation of the DNSProvider.
31 type Config struct {
32 PropagationTimeout time.Duration
33 PollingInterval time.Duration
34 BaseURL string
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, dns01.DefaultPropagationTimeout),
42 PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
43 HTTPClient: &http.Client{
44 Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute),
45 },
46 }
47 }
48
49 // DNSProvider implements the challenge.Provider interface.
50 type DNSProvider struct {
51 config *Config
52 client *bindman.DNSWebhookClient
53 }
54
55 // NewDNSProvider returns a DNSProvider instance configured for Bindman.
56 // BINDMAN_MANAGER_ADDRESS should have the scheme, hostname, and port (if required) of the authoritative Bindman Manager server.
57 func NewDNSProvider() (*DNSProvider, error) {
58 values, err := env.Get(EnvManagerAddress)
59 if err != nil {
60 return nil, fmt.Errorf("bindman: %w", err)
61 }
62
63 config := NewDefaultConfig()
64 config.BaseURL = values[EnvManagerAddress]
65
66 return NewDNSProviderConfig(config)
67 }
68
69 // NewDNSProviderConfig return a DNSProvider instance configured for Bindman.
70 func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
71 if config == nil {
72 return nil, errors.New("bindman: the configuration of the DNS provider is nil")
73 }
74
75 if config.BaseURL == "" {
76 return nil, errors.New("bindman: bindman manager address missing")
77 }
78
79 // Because the client.New uses the http.DefaultClient.
80 if config.HTTPClient == nil {
81 config.HTTPClient = &http.Client{Timeout: time.Minute}
82 }
83
84 client, err := bindman.New(config.BaseURL, clientdebug.Wrap(config.HTTPClient))
85 if err != nil {
86 return nil, fmt.Errorf("bindman: %w", err)
87 }
88
89 return &DNSProvider{config: config, client: client}, nil
90 }
91
92 // Present creates a TXT record using the specified parameters.
93 // This will *not* create a subzone to contain the TXT record,
94 // so make sure the FQDN specified is within an extant zone.
95 func (d *DNSProvider) Present(domain, token, keyAuth string) error {
96 info := dns01.GetChallengeInfo(domain, keyAuth)
97
98 if err := d.client.AddRecord(info.EffectiveFQDN, "TXT", info.Value); err != nil {
99 return fmt.Errorf("bindman: %w", err)
100 }
101
102 return nil
103 }
104
105 // CleanUp removes the TXT record matching the specified parameters.
106 func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
107 info := dns01.GetChallengeInfo(domain, keyAuth)
108
109 if err := d.client.RemoveRecord(info.EffectiveFQDN, "TXT"); err != nil {
110 return fmt.Errorf("bindman: %w", err)
111 }
112
113 return nil
114 }
115
116 // Timeout returns the timeout and interval to use when checking for DNS propagation.
117 // Adjusting here to cope with spikes in propagation times.
118 func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
119 return d.config.PropagationTimeout, d.config.PollingInterval
120 }
121