Skip to content

Commit 466276f

Browse files
committed
Add DNS provider for Core-Networks
1 parent 83ff393 commit 466276f

File tree

13 files changed

+880
-0
lines changed

13 files changed

+880
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package corenetworks
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
"time"
9+
10+
"github.com/go-acme/lego/v4/challenge/dns01"
11+
"github.com/go-acme/lego/v4/platform/config/env"
12+
"github.com/go-acme/lego/v4/providers/dns/corenetworks/internal"
13+
)
14+
15+
// Environment variables names.
16+
const (
17+
envNamespace = "CORENETWORKS_"
18+
19+
EnvLogin = envNamespace + "LOGIN"
20+
EnvPassword = envNamespace + "PASSWORD"
21+
22+
EnvTTL = envNamespace + "TTL"
23+
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
24+
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
25+
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
26+
)
27+
28+
// Config is used to configure the creation of the DNSProvider.
29+
type Config struct {
30+
Login string
31+
Password string
32+
PropagationTimeout time.Duration
33+
PollingInterval time.Duration
34+
TTL int
35+
HTTPClient *http.Client
36+
}
37+
38+
// NewDefaultConfig returns a default configuration for the DNSProvider.
39+
func NewDefaultConfig() *Config {
40+
return &Config{
41+
TTL: env.GetOrDefaultInt(EnvTTL, 3600),
42+
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
43+
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
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 Core-Networks.
57+
// Credentials must be passed in the environment variables: CORENETWORKS_LOGIN, CORENETWORKS_PASSWORD.
58+
func NewDNSProvider() (*DNSProvider, error) {
59+
values, err := env.Get(EnvLogin, EnvPassword)
60+
if err != nil {
61+
return nil, fmt.Errorf("corenetworks: %w", err)
62+
}
63+
64+
config := NewDefaultConfig()
65+
config.Login = values[EnvLogin]
66+
config.Password = values[EnvPassword]
67+
68+
return NewDNSProviderConfig(config)
69+
}
70+
71+
// NewDNSProviderConfig return a DNSProvider instance configured for Bluecat DNS.
72+
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
73+
if config == nil {
74+
return nil, errors.New("corenetworks: the configuration of the DNS provider is nil")
75+
}
76+
77+
if config.Login == "" || config.Password == "" {
78+
return nil, errors.New("corenetworks: credentials missing")
79+
}
80+
81+
client := internal.NewClient(config.Login, config.Password)
82+
83+
if config.HTTPClient != nil {
84+
client.HTTPClient = config.HTTPClient
85+
}
86+
87+
return &DNSProvider{config: config, client: client}, nil
88+
}
89+
90+
// Timeout returns the timeout and interval to use when checking for DNS propagation.
91+
// Adjusting here to cope with spikes in propagation times.
92+
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
93+
return d.config.PropagationTimeout, d.config.PollingInterval
94+
}
95+
96+
// Present creates a TXT record using the specified parameters.
97+
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
98+
info := dns01.GetChallengeInfo(domain, keyAuth)
99+
100+
ctx, err := d.client.CreateAuthenticatedContext(context.Background())
101+
if err != nil {
102+
return fmt.Errorf("create authentication token: %w", err)
103+
}
104+
105+
zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
106+
if err != nil {
107+
return fmt.Errorf("corenetworks: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
108+
}
109+
110+
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone)
111+
if err != nil {
112+
return fmt.Errorf("corenetworks: %w", err)
113+
}
114+
115+
record := internal.Record{
116+
Name: subDomain,
117+
TTL: d.config.TTL,
118+
Type: "TXT",
119+
Data: info.Value,
120+
}
121+
122+
err = d.client.AddRecord(ctx, zone, record)
123+
if err != nil {
124+
return fmt.Errorf("corenetworks: add record:%w", err)
125+
}
126+
127+
err = d.client.CommitRecords(ctx, zone)
128+
if err != nil {
129+
return fmt.Errorf("corenetworks: commit records: %w", err)
130+
}
131+
132+
return nil
133+
}
134+
135+
// CleanUp removes the TXT record matching the specified parameters.
136+
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
137+
info := dns01.GetChallengeInfo(domain, keyAuth)
138+
139+
ctx, err := d.client.CreateAuthenticatedContext(context.Background())
140+
if err != nil {
141+
return fmt.Errorf("create authentication token: %w", err)
142+
}
143+
144+
zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
145+
if err != nil {
146+
return fmt.Errorf("corenetworks: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
147+
}
148+
149+
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone)
150+
if err != nil {
151+
return fmt.Errorf("corenetworks: %w", err)
152+
}
153+
154+
record := internal.Record{
155+
Name: subDomain,
156+
TTL: d.config.TTL,
157+
Type: "TXT",
158+
Data: info.Value,
159+
}
160+
161+
err = d.client.DeleteRecords(ctx, zone, record)
162+
if err != nil {
163+
return fmt.Errorf("corenetworks: delete records: %w", err)
164+
}
165+
166+
err = d.client.CommitRecords(ctx, zone)
167+
if err != nil {
168+
return fmt.Errorf("corenetworks: commit records: %w", err)
169+
}
170+
171+
return nil
172+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Name = "Core-Networks"
2+
Description = ''''''
3+
URL = "https://www.core-networks.de/"
4+
Code = "corenetworks"
5+
Since = "v4.16.0"
6+
7+
Example = '''
8+
CORENETWORKS_LOGIN="xxxx" \
9+
CORENETWORKS_PASSWORD="yyyy" \
10+
lego --email myemail@example.com --dns corenetworks --domains my.example.org run
11+
'''
12+
13+
[Configuration]
14+
[Configuration.Credentials]
15+
CORENETWORKS_LOGIN = "The username of the API account"
16+
CORENETWORKS_PASSWORD = "The password"
17+
[Configuration.Additional]
18+
CORENETWORKS_POLLING_INTERVAL = "Time between DNS propagation check"
19+
CORENETWORKS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
20+
CORENETWORKS_TTL = "The TTL of the TXT record used for the DNS challenge"
21+
CORENETWORKS_HTTP_TIMEOUT = "API request timeout"
22+
23+
[Links]
24+
API = "https://beta.api.core-networks.de/doc/"
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package corenetworks
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(EnvLogin, 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+
EnvLogin: "user",
24+
EnvPassword: "secret",
25+
},
26+
},
27+
{
28+
desc: "missing login",
29+
envVars: map[string]string{
30+
EnvPassword: "secret",
31+
},
32+
expected: "corenetworks: some credentials information are missing: CORENETWORKS_LOGIN",
33+
},
34+
{
35+
desc: "missing password",
36+
envVars: map[string]string{
37+
EnvLogin: "user",
38+
},
39+
expected: "corenetworks: some credentials information are missing: CORENETWORKS_PASSWORD",
40+
},
41+
}
42+
43+
for _, test := range testCases {
44+
t.Run(test.desc, func(t *testing.T) {
45+
defer envTest.RestoreEnv()
46+
envTest.ClearEnv()
47+
48+
envTest.Apply(test.envVars)
49+
50+
p, err := NewDNSProvider()
51+
52+
if test.expected == "" {
53+
require.NoError(t, err)
54+
require.NotNil(t, p)
55+
require.NotNil(t, p.config)
56+
require.NotNil(t, p.client)
57+
} else {
58+
require.EqualError(t, err, test.expected)
59+
}
60+
})
61+
}
62+
}
63+
64+
func TestNewDNSProviderConfig(t *testing.T) {
65+
testCases := []struct {
66+
desc string
67+
login string
68+
password string
69+
expected string
70+
}{
71+
{
72+
desc: "success",
73+
login: "user",
74+
password: "secret",
75+
},
76+
{
77+
desc: "missing login",
78+
password: "secret",
79+
expected: "corenetworks: credentials missing",
80+
},
81+
{
82+
desc: "missing password",
83+
login: "user",
84+
expected: "corenetworks: credentials missing",
85+
},
86+
}
87+
88+
for _, test := range testCases {
89+
t.Run(test.desc, func(t *testing.T) {
90+
config := NewDefaultConfig()
91+
config.Login = test.login
92+
config.Password = test.password
93+
94+
p, err := NewDNSProviderConfig(config)
95+
96+
if test.expected == "" {
97+
require.NoError(t, err)
98+
require.NotNil(t, p)
99+
require.NotNil(t, p.config)
100+
require.NotNil(t, p.client)
101+
} else {
102+
require.EqualError(t, err, test.expected)
103+
}
104+
})
105+
}
106+
}
107+
108+
func TestLivePresent(t *testing.T) {
109+
if !envTest.IsLiveTest() {
110+
t.Skip("skipping live test")
111+
}
112+
113+
envTest.RestoreEnv()
114+
provider, err := NewDNSProvider()
115+
require.NoError(t, err)
116+
117+
err = provider.Present(envTest.GetDomain(), "", "123d==")
118+
require.NoError(t, err)
119+
}
120+
121+
func TestLiveCleanUp(t *testing.T) {
122+
if !envTest.IsLiveTest() {
123+
t.Skip("skipping live test")
124+
}
125+
126+
envTest.RestoreEnv()
127+
provider, err := NewDNSProvider()
128+
require.NoError(t, err)
129+
130+
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
131+
require.NoError(t, err)
132+
}

0 commit comments

Comments
 (0)