infoblox.go raw
1 // Package infoblox implements a DNS provider for solving the DNS-01 challenge using on prem infoblox DNS.
2 package infoblox
3
4 import (
5 "errors"
6 "fmt"
7 "strconv"
8 "sync"
9 "time"
10
11 "github.com/go-acme/lego/v4/challenge"
12 "github.com/go-acme/lego/v4/challenge/dns01"
13 "github.com/go-acme/lego/v4/platform/config/env"
14 "github.com/go-acme/lego/v4/providers/dns/internal/useragent"
15 infoblox "github.com/infobloxopen/infoblox-go-client/v2"
16 )
17
18 // Environment variables names.
19 const (
20 envNamespace = "INFOBLOX_"
21
22 EnvHost = envNamespace + "HOST"
23 EnvPort = envNamespace + "PORT"
24 EnvUsername = envNamespace + "USERNAME"
25 EnvPassword = envNamespace + "PASSWORD"
26 EnvDNSView = envNamespace + "DNS_VIEW"
27 EnvWApiVersion = envNamespace + "WAPI_VERSION"
28 EnvSSLVerify = envNamespace + "SSL_VERIFY"
29 EnvCACertificate = envNamespace + "CA_CERTIFICATE"
30
31 EnvTTL = envNamespace + "TTL"
32 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
33 EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
34 EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
35 )
36
37 const defaultPoolConnections = 10
38
39 var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
40
41 // Config is used to configure the creation of the DNSProvider.
42 type Config struct {
43 // Host is the URL of the grid manager.
44 Host string
45 // Port is the Port for the grid manager.
46 Port string
47
48 // Username the user for accessing API.
49 Username string
50 // Password the password for accessing API.
51 Password string
52
53 // DNSView is the dns view to put new records and search from.
54 DNSView string
55 // WapiVersion is the version of web api used.
56 WapiVersion string
57
58 // SSLVerify is whether or not to verify the ssl of the server being hit.
59 SSLVerify bool
60
61 // CACertificate is the path to the CA certificate (PEM encoded).
62 CACertificate string
63
64 PropagationTimeout time.Duration
65 PollingInterval time.Duration
66 TTL int
67 HTTPTimeout int
68 }
69
70 // NewDefaultConfig returns a default configuration for the DNSProvider.
71 func NewDefaultConfig() *Config {
72 return &Config{
73 DNSView: env.GetOrDefaultString(EnvDNSView, "External"),
74 WapiVersion: env.GetOrDefaultString(EnvWApiVersion, "2.11"),
75 Port: env.GetOrDefaultString(EnvPort, "443"),
76 SSLVerify: env.GetOrDefaultBool(EnvSSLVerify, true),
77 CACertificate: env.GetOrDefaultString(EnvCACertificate, ""),
78
79 TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
80 PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
81 PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
82 HTTPTimeout: env.GetOrDefaultInt(EnvHTTPTimeout, 30),
83 }
84 }
85
86 // DNSProvider implements the challenge.Provider interface.
87 type DNSProvider struct {
88 config *Config
89 transportConfig infoblox.TransportConfig
90 ibConfig infoblox.HostConfig
91 ibAuth infoblox.AuthConfig
92
93 recordRefs map[string]string
94 recordRefsMu sync.Mutex
95 }
96
97 // NewDNSProvider returns a DNSProvider instance configured for Infoblox.
98 // Credentials must be passed in the environment variables:
99 // INFOBLOX_USERNAME, INFOBLOX_PASSWORD
100 // INFOBLOX_HOST, INFOBLOX_PORT
101 // INFOBLOX_DNS_VIEW, INFOBLOX_WAPI_VERSION
102 // INFOBLOX_SSL_VERIFY.
103 func NewDNSProvider() (*DNSProvider, error) {
104 values, err := env.Get(EnvHost, EnvUsername, EnvPassword)
105 if err != nil {
106 return nil, fmt.Errorf("infoblox: %w", err)
107 }
108
109 config := NewDefaultConfig()
110 config.Host = values[EnvHost]
111 config.Username = values[EnvUsername]
112 config.Password = values[EnvPassword]
113
114 return NewDNSProviderConfig(config)
115 }
116
117 // NewDNSProviderConfig return a DNSProvider instance configured for HyperOne.
118 func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
119 if config == nil {
120 return nil, errors.New("infoblox: the configuration of the DNS provider is nil")
121 }
122
123 if config.Host == "" {
124 return nil, errors.New("infoblox: missing host")
125 }
126
127 if config.Username == "" || config.Password == "" {
128 return nil, errors.New("infoblox: missing credentials")
129 }
130
131 var sslVerify string
132 if config.CACertificate != "" {
133 sslVerify = config.CACertificate
134 } else {
135 sslVerify = strconv.FormatBool(config.SSLVerify)
136 }
137
138 return &DNSProvider{
139 config: config,
140 transportConfig: infoblox.NewTransportConfig(sslVerify, config.HTTPTimeout, defaultPoolConnections),
141 ibConfig: infoblox.HostConfig{
142 Host: config.Host,
143 Version: config.WapiVersion,
144 Port: config.Port,
145 },
146 ibAuth: infoblox.AuthConfig{
147 Username: config.Username,
148 Password: config.Password,
149 },
150 recordRefs: make(map[string]string),
151 }, nil
152 }
153
154 // Timeout returns the timeout and interval to use when checking for DNS propagation.
155 func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
156 return d.config.PropagationTimeout, d.config.PollingInterval
157 }
158
159 // Present creates a TXT record to fulfill the dns-01 challenge.
160 func (d *DNSProvider) Present(domain, token, keyAuth string) error {
161 info := dns01.GetChallengeInfo(domain, keyAuth)
162
163 connector, err := infoblox.NewConnector(d.ibConfig, d.ibAuth, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{})
164 if err != nil {
165 return fmt.Errorf("infoblox: %w", err)
166 }
167
168 defer func() { _ = connector.Logout() }()
169
170 objectManager := infoblox.NewObjectManager(connector, useragent.Get(), "")
171
172 record, err := objectManager.CreateTXTRecord(d.config.DNSView, dns01.UnFqdn(info.EffectiveFQDN), info.Value, uint32(d.config.TTL), true, "lego", nil)
173 if err != nil {
174 return fmt.Errorf("infoblox: could not create TXT record for %s: %w", domain, err)
175 }
176
177 d.recordRefsMu.Lock()
178 d.recordRefs[token] = record.Ref
179 d.recordRefsMu.Unlock()
180
181 return nil
182 }
183
184 // CleanUp removes the TXT record matching the specified parameters.
185 func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
186 info := dns01.GetChallengeInfo(domain, keyAuth)
187
188 connector, err := infoblox.NewConnector(d.ibConfig, d.ibAuth, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{})
189 if err != nil {
190 return fmt.Errorf("infoblox: %w", err)
191 }
192
193 defer func() { _ = connector.Logout() }()
194
195 objectManager := infoblox.NewObjectManager(connector, useragent.Get(), "")
196
197 // gets the record's unique ref from when we created it
198 d.recordRefsMu.Lock()
199 recordRef, ok := d.recordRefs[token]
200 d.recordRefsMu.Unlock()
201
202 if !ok {
203 return fmt.Errorf("infoblox: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token)
204 }
205
206 _, err = objectManager.DeleteTXTRecord(recordRef)
207 if err != nil {
208 return fmt.Errorf("infoblox: could not delete TXT record for %s: %w", domain, err)
209 }
210
211 // Delete record ref from map
212 d.recordRefsMu.Lock()
213 delete(d.recordRefs, token)
214 d.recordRefsMu.Unlock()
215
216 return nil
217 }
218