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