~hduran-8/+junk/caddylegacy

« back to all changes in this revision

Viewing changes to debian/gocode/src/github.com/xenolf/lego/providers/dns/pdns/pdns.go

  • Committer: Horacio Durán
  • Date: 2016-10-14 14:33:43 UTC
  • Revision ID: horacio.duran@canonical.com-20161014143343-ytyhz5sx7d1cje4q
Added new upstream version

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Package pdns implements a DNS provider for solving the DNS-01
 
2
// challenge using PowerDNS nameserver.
 
3
package pdns
 
4
 
 
5
import (
 
6
        "bytes"
 
7
        "encoding/json"
 
8
        "fmt"
 
9
        "io"
 
10
        "net/http"
 
11
        "net/url"
 
12
        "os"
 
13
        "strconv"
 
14
        "strings"
 
15
        "time"
 
16
 
 
17
        "github.com/xenolf/lego/acme"
 
18
)
 
19
 
 
20
// DNSProvider is an implementation of the acme.ChallengeProvider interface
 
21
type DNSProvider struct {
 
22
        apiKey     string
 
23
        host       *url.URL
 
24
        apiVersion int
 
25
}
 
26
 
 
27
// NewDNSProvider returns a DNSProvider instance configured for pdns.
 
28
// Credentials must be passed in the environment variable:
 
29
// PDNS_API_URL and PDNS_API_KEY.
 
30
func NewDNSProvider() (*DNSProvider, error) {
 
31
        key := os.Getenv("PDNS_API_KEY")
 
32
        hostUrl, err := url.Parse(os.Getenv("PDNS_API_URL"))
 
33
        if err != nil {
 
34
                return nil, err
 
35
        }
 
36
 
 
37
        return NewDNSProviderCredentials(hostUrl, key)
 
38
}
 
39
 
 
40
// NewDNSProviderCredentials uses the supplied credentials to return a
 
41
// DNSProvider instance configured for pdns.
 
42
func NewDNSProviderCredentials(host *url.URL, key string) (*DNSProvider, error) {
 
43
        if key == "" {
 
44
                return nil, fmt.Errorf("PDNS API key missing")
 
45
        }
 
46
 
 
47
        if host == nil || host.Host == "" {
 
48
                return nil, fmt.Errorf("PDNS API URL missing")
 
49
        }
 
50
 
 
51
        provider := &DNSProvider{
 
52
                host:   host,
 
53
                apiKey: key,
 
54
        }
 
55
        provider.getAPIVersion()
 
56
 
 
57
        return provider, nil
 
58
}
 
59
 
 
60
// Timeout returns the timeout and interval to use when checking for DNS
 
61
// propagation. Adjusting here to cope with spikes in propagation times.
 
62
func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
 
63
        return 120 * time.Second, 2 * time.Second
 
64
}
 
65
 
 
66
// Present creates a TXT record to fulfil the dns-01 challenge
 
67
func (c *DNSProvider) Present(domain, token, keyAuth string) error {
 
68
        fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
 
69
        zone, err := c.getHostedZone(fqdn)
 
70
        if err != nil {
 
71
                return err
 
72
        }
 
73
 
 
74
        name := fqdn
 
75
 
 
76
        // pre-v1 API wants non-fqdn
 
77
        if c.apiVersion == 0 {
 
78
                name = acme.UnFqdn(fqdn)
 
79
        }
 
80
 
 
81
        rec := pdnsRecord{
 
82
                Content:  "\"" + value + "\"",
 
83
                Disabled: false,
 
84
 
 
85
                // pre-v1 API
 
86
                Type: "TXT",
 
87
                Name: name,
 
88
                TTL:  120,
 
89
        }
 
90
 
 
91
        rrsets := rrSets{
 
92
                RRSets: []rrSet{
 
93
                        rrSet{
 
94
                                Name:       name,
 
95
                                ChangeType: "REPLACE",
 
96
                                Type:       "TXT",
 
97
                                Kind:       "Master",
 
98
                                TTL:        120,
 
99
                                Records:    []pdnsRecord{rec},
 
100
                        },
 
101
                },
 
102
        }
 
103
 
 
104
        body, err := json.Marshal(rrsets)
 
105
        if err != nil {
 
106
                return err
 
107
        }
 
108
 
 
109
        _, err = c.makeRequest("PATCH", zone.URL, bytes.NewReader(body))
 
110
        if err != nil {
 
111
                fmt.Println("here")
 
112
                return err
 
113
        }
 
114
 
 
115
        return nil
 
116
}
 
117
 
 
118
// CleanUp removes the TXT record matching the specified parameters
 
119
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 
120
        fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
 
121
 
 
122
        zone, err := c.getHostedZone(fqdn)
 
123
        if err != nil {
 
124
                return err
 
125
        }
 
126
 
 
127
        set, err := c.findTxtRecord(fqdn)
 
128
        if err != nil {
 
129
                return err
 
130
        }
 
131
 
 
132
        rrsets := rrSets{
 
133
                RRSets: []rrSet{
 
134
                        rrSet{
 
135
                                Name:       set.Name,
 
136
                                Type:       set.Type,
 
137
                                ChangeType: "DELETE",
 
138
                        },
 
139
                },
 
140
        }
 
141
        body, err := json.Marshal(rrsets)
 
142
        if err != nil {
 
143
                return err
 
144
        }
 
145
 
 
146
        _, err = c.makeRequest("PATCH", zone.URL, bytes.NewReader(body))
 
147
        if err != nil {
 
148
                return err
 
149
        }
 
150
 
 
151
        return nil
 
152
}
 
153
 
 
154
func (c *DNSProvider) getHostedZone(fqdn string) (*hostedZone, error) {
 
155
        var zone hostedZone
 
156
        authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
 
157
        if err != nil {
 
158
                return nil, err
 
159
        }
 
160
 
 
161
        url := "/servers/localhost/zones"
 
162
        result, err := c.makeRequest("GET", url, nil)
 
163
        if err != nil {
 
164
                return nil, err
 
165
        }
 
166
 
 
167
        zones := []hostedZone{}
 
168
        err = json.Unmarshal(result, &zones)
 
169
        if err != nil {
 
170
                return nil, err
 
171
        }
 
172
 
 
173
        url = ""
 
174
        for _, zone := range zones {
 
175
                if acme.UnFqdn(zone.Name) == acme.UnFqdn(authZone) {
 
176
                        url = zone.URL
 
177
                }
 
178
        }
 
179
 
 
180
        result, err = c.makeRequest("GET", url, nil)
 
181
        if err != nil {
 
182
                return nil, err
 
183
        }
 
184
 
 
185
        err = json.Unmarshal(result, &zone)
 
186
        if err != nil {
 
187
                return nil, err
 
188
        }
 
189
 
 
190
        // convert pre-v1 API result
 
191
        if len(zone.Records) > 0 {
 
192
                zone.RRSets = []rrSet{}
 
193
                for _, record := range zone.Records {
 
194
                        set := rrSet{
 
195
                                Name:    record.Name,
 
196
                                Type:    record.Type,
 
197
                                Records: []pdnsRecord{record},
 
198
                        }
 
199
                        zone.RRSets = append(zone.RRSets, set)
 
200
                }
 
201
        }
 
202
 
 
203
        return &zone, nil
 
204
}
 
205
 
 
206
func (c *DNSProvider) findTxtRecord(fqdn string) (*rrSet, error) {
 
207
        zone, err := c.getHostedZone(fqdn)
 
208
        if err != nil {
 
209
                return nil, err
 
210
        }
 
211
 
 
212
        _, err = c.makeRequest("GET", zone.URL, nil)
 
213
        if err != nil {
 
214
                return nil, err
 
215
        }
 
216
 
 
217
        for _, set := range zone.RRSets {
 
218
                if (set.Name == acme.UnFqdn(fqdn) || set.Name == fqdn) && set.Type == "TXT" {
 
219
                        return &set, nil
 
220
                }
 
221
        }
 
222
 
 
223
        return nil, fmt.Errorf("No existing record found for %s", fqdn)
 
224
}
 
225
 
 
226
func (c *DNSProvider) getAPIVersion() {
 
227
        type APIVersion struct {
 
228
                URL     string `json:"url"`
 
229
                Version int    `json:"version"`
 
230
        }
 
231
 
 
232
        result, err := c.makeRequest("GET", "/api", nil)
 
233
        if err != nil {
 
234
                return
 
235
        }
 
236
 
 
237
        var versions []APIVersion
 
238
        err = json.Unmarshal(result, &versions)
 
239
        if err != nil {
 
240
                return
 
241
        }
 
242
 
 
243
        latestVersion := 0
 
244
        for _, v := range versions {
 
245
                if v.Version > latestVersion {
 
246
                        latestVersion = v.Version
 
247
                }
 
248
        }
 
249
        c.apiVersion = latestVersion
 
250
}
 
251
 
 
252
func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
 
253
        type APIError struct {
 
254
                Error string `json:"error"`
 
255
        }
 
256
        var path = ""
 
257
        if c.host.Path != "/" {
 
258
                path = c.host.Path
 
259
        }
 
260
        if c.apiVersion > 0 {
 
261
                if !strings.HasPrefix(uri, "api/v") {
 
262
                        uri = "/api/v" + strconv.Itoa(c.apiVersion) + uri
 
263
                } else {
 
264
                        uri = "/" + uri
 
265
                }
 
266
        }
 
267
        url := c.host.Scheme + "://" + c.host.Host + path + uri
 
268
        req, err := http.NewRequest(method, url, body)
 
269
        if err != nil {
 
270
                return nil, err
 
271
        }
 
272
 
 
273
        req.Header.Set("X-API-Key", c.apiKey)
 
274
 
 
275
        client := http.Client{Timeout: 30 * time.Second}
 
276
        resp, err := client.Do(req)
 
277
        if err != nil {
 
278
                return nil, fmt.Errorf("Error talking to PDNS API -> %v", err)
 
279
        }
 
280
 
 
281
        defer resp.Body.Close()
 
282
 
 
283
        if resp.StatusCode != 422 && (resp.StatusCode < 200 || resp.StatusCode >= 300) {
 
284
                return nil, fmt.Errorf("Unexpected HTTP status code %d when fetching '%s'", resp.StatusCode, url)
 
285
        }
 
286
 
 
287
        var msg json.RawMessage
 
288
        err = json.NewDecoder(resp.Body).Decode(&msg)
 
289
        switch {
 
290
        case err == io.EOF:
 
291
                // empty body
 
292
                return nil, nil
 
293
        case err != nil:
 
294
                // other error
 
295
                return nil, err
 
296
        }
 
297
 
 
298
        // check for PowerDNS error message
 
299
        if len(msg) > 0 && msg[0] == '{' {
 
300
                var apiError APIError
 
301
                err = json.Unmarshal(msg, &apiError)
 
302
                if err != nil {
 
303
                        return nil, err
 
304
                }
 
305
                if apiError.Error != "" {
 
306
                        return nil, fmt.Errorf("Error talking to PDNS API -> %v", apiError.Error)
 
307
                }
 
308
        }
 
309
        return msg, nil
 
310
}
 
311
 
 
312
type pdnsRecord struct {
 
313
        Content  string `json:"content"`
 
314
        Disabled bool   `json:"disabled"`
 
315
 
 
316
        // pre-v1 API
 
317
        Name string `json:"name"`
 
318
        Type string `json:"type"`
 
319
        TTL  int    `json:"ttl,omitempty"`
 
320
}
 
321
 
 
322
type hostedZone struct {
 
323
        ID     string  `json:"id"`
 
324
        Name   string  `json:"name"`
 
325
        URL    string  `json:"url"`
 
326
        RRSets []rrSet `json:"rrsets"`
 
327
 
 
328
        // pre-v1 API
 
329
        Records []pdnsRecord `json:"records"`
 
330
}
 
331
 
 
332
type rrSet struct {
 
333
        Name       string       `json:"name"`
 
334
        Type       string       `json:"type"`
 
335
        Kind       string       `json:"kind"`
 
336
        ChangeType string       `json:"changetype"`
 
337
        Records    []pdnsRecord `json:"records"`
 
338
        TTL        int          `json:"ttl,omitempty"`
 
339
}
 
340
 
 
341
type rrSets struct {
 
342
        RRSets []rrSet `json:"rrsets"`
 
343
}