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