Skip to content

Commit 0b542ea

Browse files
committed
Add DNS provider for BookMyName
1 parent 4809501 commit 0b542ea

File tree

9 files changed

+544
-0
lines changed

9 files changed

+544
-0
lines changed
+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Package bookmyname implements a DNS provider for solving the DNS-01 challenge using BookMyName.
2+
package bookmyname
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/bookmyname/internal"
14+
)
15+
16+
// Environment variables names.
17+
const (
18+
envNamespace = "BOOKMYNAME_"
19+
20+
EnvUsername = envNamespace + "USERNAME"
21+
EnvPassword = envNamespace + "PASSWORD"
22+
23+
EnvTTL = envNamespace + "TTL"
24+
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
25+
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
26+
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
27+
)
28+
29+
// Config is used to configure the creation of the DNSProvider.
30+
type Config struct {
31+
Username string
32+
Password string
33+
34+
PropagationTimeout time.Duration
35+
PollingInterval time.Duration
36+
TTL int
37+
HTTPClient *http.Client
38+
}
39+
40+
// NewDefaultConfig returns a default configuration for the DNSProvider.
41+
func NewDefaultConfig() *Config {
42+
return &Config{
43+
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
44+
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
45+
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
46+
HTTPClient: &http.Client{
47+
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
48+
},
49+
}
50+
}
51+
52+
// DNSProvider implements the challenge.Provider interface.
53+
type DNSProvider struct {
54+
config *Config
55+
client *internal.Client
56+
}
57+
58+
// NewDNSProvider returns a DNSProvider instance configured for BookMyName.
59+
func NewDNSProvider() (*DNSProvider, error) {
60+
values, err := env.Get(EnvUsername, EnvPassword)
61+
if err != nil {
62+
return nil, fmt.Errorf("bookmyname: %w", err)
63+
}
64+
65+
config := NewDefaultConfig()
66+
config.Username = values[EnvUsername]
67+
config.Password = values[EnvPassword]
68+
69+
return NewDNSProviderConfig(config)
70+
}
71+
72+
// NewDNSProviderConfig return a DNSProvider instance configured for BookMyName.
73+
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
74+
if config == nil {
75+
return nil, errors.New("bookmyname: the configuration of the DNS provider is nil")
76+
}
77+
78+
client, err := internal.NewClient(config.Username, config.Password)
79+
if err != nil {
80+
return nil, fmt.Errorf("bookmyname: %w", err)
81+
}
82+
83+
if config.HTTPClient != nil {
84+
client.HTTPClient = config.HTTPClient
85+
}
86+
87+
return &DNSProvider{
88+
config: config,
89+
client: client,
90+
}, nil
91+
}
92+
93+
// Present creates a TXT record using the specified parameters.
94+
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
95+
info := dns01.GetChallengeInfo(domain, keyAuth)
96+
97+
record := internal.Record{
98+
Hostname: info.EffectiveFQDN,
99+
Type: "txt",
100+
TTL: d.config.TTL,
101+
Value: info.Value,
102+
}
103+
104+
err := d.client.AddRecord(context.Background(), record)
105+
if err != nil {
106+
return fmt.Errorf("bookmyname: add record: %w", err)
107+
}
108+
109+
return nil
110+
}
111+
112+
// CleanUp removes the TXT record matching the specified parameters.
113+
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
114+
info := dns01.GetChallengeInfo(domain, keyAuth)
115+
116+
record := internal.Record{
117+
Hostname: info.EffectiveFQDN,
118+
Type: "txt",
119+
TTL: d.config.TTL,
120+
Value: info.Value,
121+
}
122+
123+
err := d.client.RemoveRecord(context.Background(), record)
124+
if err != nil {
125+
return fmt.Errorf("bookmyname: add record: %w", err)
126+
}
127+
128+
return nil
129+
}
130+
131+
// Timeout returns the timeout and interval to use when checking for DNS propagation.
132+
// Adjusting here to cope with spikes in propagation times.
133+
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
134+
return d.config.PropagationTimeout, d.config.PollingInterval
135+
}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Name = "BookMyName"
2+
Description = ''''''
3+
URL = "https://www.bookmyname.com/"
4+
Code = "bookmyname"
5+
Since = "v4.20.0"
6+
7+
Example = '''
8+
BOOKMYNAME_USERNAME="xxx" \
9+
BOOKMYNAME_PASSWORD="yyy" \
10+
lego --email [email protected] --dns bookmyname --domains my.example.org run
11+
'''
12+
13+
[Configuration]
14+
[Configuration.Credentials]
15+
BOOKMYNAME_USERNAME = "Username"
16+
BOOKMYNAME_PASSWORD = "Password"
17+
[Configuration.Additional]
18+
BOOKMYNAME_POLLING_INTERVAL = "Time between DNS propagation check"
19+
BOOKMYNAME_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
20+
BOOKMYNAME_TTL = "The TTL of the TXT record used for the DNS challenge"
21+
BOOKMYNAME_HTTP_TIMEOUT = "API request timeout"
22+
23+
[Links]
24+
API = "https://fr.faqs.bookmyname.com/frfaqs/dyndns"
+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package bookmyname
2+
3+
import (
4+
"testing"
5+
6+
"github.com/go-acme/lego/v4/platform/tester"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
const envDomain = envNamespace + "DOMAIN"
11+
12+
var envTest = tester.NewEnvTest(EnvUsername, EnvPassword).WithDomain(envDomain)
13+
14+
func TestNewDNSProvider(t *testing.T) {
15+
testCases := []struct {
16+
desc string
17+
envVars map[string]string
18+
expected string
19+
}{
20+
{
21+
desc: "success",
22+
envVars: map[string]string{
23+
EnvUsername: "user",
24+
EnvPassword: "secret",
25+
},
26+
},
27+
{
28+
desc: "missing username",
29+
envVars: map[string]string{
30+
EnvUsername: "",
31+
EnvPassword: "secret",
32+
},
33+
expected: "bookmyname: some credentials information are missing: BOOKMYNAME_USERNAME",
34+
},
35+
{
36+
desc: "missing paswword",
37+
envVars: map[string]string{
38+
EnvUsername: "user",
39+
EnvPassword: "",
40+
},
41+
expected: "bookmyname: some credentials information are missing: BOOKMYNAME_PASSWORD",
42+
},
43+
{
44+
desc: "missing credentials",
45+
envVars: map[string]string{},
46+
expected: "bookmyname: some credentials information are missing: BOOKMYNAME_USERNAME,BOOKMYNAME_PASSWORD",
47+
},
48+
}
49+
50+
for _, test := range testCases {
51+
t.Run(test.desc, func(t *testing.T) {
52+
defer envTest.RestoreEnv()
53+
envTest.ClearEnv()
54+
55+
envTest.Apply(test.envVars)
56+
57+
p, err := NewDNSProvider()
58+
59+
if test.expected == "" {
60+
require.NoError(t, err)
61+
require.NotNil(t, p)
62+
require.NotNil(t, p.config)
63+
require.NotNil(t, p.client)
64+
} else {
65+
require.EqualError(t, err, test.expected)
66+
}
67+
})
68+
}
69+
}
70+
71+
func TestNewDNSProviderConfig(t *testing.T) {
72+
testCases := []struct {
73+
desc string
74+
username string
75+
password string
76+
expected string
77+
}{
78+
{
79+
desc: "success",
80+
username: "user",
81+
password: "secret",
82+
},
83+
{
84+
desc: "missing username",
85+
password: "secret",
86+
expected: "bookmyname: credentials missing",
87+
},
88+
{
89+
desc: "missing password",
90+
username: "user",
91+
expected: "bookmyname: credentials missing",
92+
},
93+
{
94+
desc: "missing credentials",
95+
expected: "bookmyname: credentials missing",
96+
},
97+
}
98+
99+
for _, test := range testCases {
100+
t.Run(test.desc, func(t *testing.T) {
101+
config := NewDefaultConfig()
102+
config.Username = test.username
103+
config.Password = test.password
104+
105+
p, err := NewDNSProviderConfig(config)
106+
107+
if test.expected == "" {
108+
require.NoError(t, err)
109+
require.NotNil(t, p)
110+
require.NotNil(t, p.config)
111+
require.NotNil(t, p.client)
112+
} else {
113+
require.EqualError(t, err, test.expected)
114+
}
115+
})
116+
}
117+
}
118+
119+
func TestLivePresent(t *testing.T) {
120+
if !envTest.IsLiveTest() {
121+
t.Skip("skipping live test")
122+
}
123+
124+
envTest.RestoreEnv()
125+
provider, err := NewDNSProvider()
126+
require.NoError(t, err)
127+
128+
err = provider.Present(envTest.GetDomain(), "", "123d==")
129+
require.NoError(t, err)
130+
}
131+
132+
func TestLiveCleanUp(t *testing.T) {
133+
if !envTest.IsLiveTest() {
134+
t.Skip("skipping live test")
135+
}
136+
137+
envTest.RestoreEnv()
138+
provider, err := NewDNSProvider()
139+
require.NoError(t, err)
140+
141+
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
142+
require.NoError(t, err)
143+
}

0 commit comments

Comments
 (0)