ultradns.go raw
1 // Package ultradns implements a DNS provider for solving the DNS-01 challenge using ultradns.
2 package ultradns
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/useragent"
14 "github.com/ultradns/ultradns-go-sdk/pkg/client"
15 "github.com/ultradns/ultradns-go-sdk/pkg/record"
16 "github.com/ultradns/ultradns-go-sdk/pkg/rrset"
17 )
18
19 // Environment variables names.
20 const (
21 envNamespace = "ULTRADNS_"
22
23 EnvUsername = envNamespace + "USERNAME"
24 EnvPassword = envNamespace + "PASSWORD"
25 EnvEndpoint = envNamespace + "ENDPOINT"
26
27 EnvTTL = envNamespace + "TTL"
28 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
29 EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
30 )
31
32 const defaultEndpoint = "https://api.ultradns.com/"
33
34 var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
35
36 // DNSProvider implements the challenge.Provider interface.
37 type DNSProvider struct {
38 config *Config
39 client *client.Client
40 }
41
42 // Config is used to configure the creation of the DNSProvider.
43 type Config struct {
44 Username string
45 Password string
46 Endpoint string
47
48 TTL int
49 PropagationTimeout time.Duration
50 PollingInterval time.Duration
51 }
52
53 // NewDefaultConfig returns a default configuration for the DNSProvider.
54 func NewDefaultConfig() *Config {
55 return &Config{
56 Endpoint: env.GetOrDefaultString(EnvEndpoint, defaultEndpoint),
57 TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
58 PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
59 PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 4*time.Second),
60 }
61 }
62
63 // NewDNSProvider returns a DNSProvider instance configured for ultradns.
64 // Credentials must be passed in the environment variables:
65 // ULTRADNS_USERNAME and ULTRADNS_PASSWORD.
66 func NewDNSProvider() (*DNSProvider, error) {
67 values, err := env.Get(EnvUsername, EnvPassword)
68 if err != nil {
69 return nil, fmt.Errorf("ultradns: %w", err)
70 }
71
72 config := NewDefaultConfig()
73 config.Username = values[EnvUsername]
74 config.Password = values[EnvPassword]
75
76 return NewDNSProviderConfig(config)
77 }
78
79 // NewDNSProviderConfig return a DNSProvider instance configured for ultradns.
80 func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
81 if config == nil {
82 return nil, errors.New("ultradns: the configuration of the DNS provider is nil")
83 }
84
85 ultraConfig := client.Config{
86 Username: config.Username,
87 Password: config.Password,
88 HostURL: config.Endpoint,
89 UserAgent: useragent.Get(),
90 }
91
92 uClient, err := client.NewClient(ultraConfig)
93 if err != nil {
94 return nil, fmt.Errorf("ultradns: %w", err)
95 }
96
97 return &DNSProvider{config: config, client: uClient}, nil
98 }
99
100 // Timeout returns the timeout and interval to use when checking for DNS propagation.
101 func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
102 return d.config.PropagationTimeout, d.config.PollingInterval
103 }
104
105 // Present creates a TXT record using the specified parameters.
106 func (d *DNSProvider) Present(domain, token, keyAuth string) error {
107 info := dns01.GetChallengeInfo(domain, keyAuth)
108
109 authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
110 if err != nil {
111 return fmt.Errorf("ultradns: could not find zone for domain %q: %w", domain, err)
112 }
113
114 recordService, err := record.Get(d.client)
115 if err != nil {
116 return fmt.Errorf("ultradns: %w", err)
117 }
118
119 rrSetKeyData := &rrset.RRSetKey{
120 Owner: info.EffectiveFQDN,
121 Zone: authZone,
122 RecordType: "TXT",
123 }
124
125 resp, _, _ := recordService.Read(rrSetKeyData)
126
127 rrSetData := &rrset.RRSet{
128 OwnerName: info.EffectiveFQDN,
129 TTL: d.config.TTL,
130 RRType: "TXT",
131 RData: []string{info.Value},
132 }
133
134 if resp != nil && resp.StatusCode == http.StatusOK {
135 _, err = recordService.Update(rrSetKeyData, rrSetData)
136 } else {
137 _, err = recordService.Create(rrSetKeyData, rrSetData)
138 }
139
140 if err != nil {
141 return fmt.Errorf("ultradns: %w", err)
142 }
143
144 return nil
145 }
146
147 // CleanUp removes the TXT record matching the specified parameters.
148 func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
149 info := dns01.GetChallengeInfo(domain, keyAuth)
150
151 authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
152 if err != nil {
153 return fmt.Errorf("ultradns: could not find zone for domain %q: %w", domain, err)
154 }
155
156 recordService, err := record.Get(d.client)
157 if err != nil {
158 return fmt.Errorf("ultradns: %w", err)
159 }
160
161 rrSetKeyData := &rrset.RRSetKey{
162 Owner: info.EffectiveFQDN,
163 Zone: authZone,
164 RecordType: "TXT",
165 }
166
167 _, err = recordService.Delete(rrSetKeyData)
168 if err != nil {
169 return fmt.Errorf("ultradns: %w", err)
170 }
171
172 return nil
173 }
174