diff --git a/Gopkg.lock b/Gopkg.lock index ba40ade13..c8b9f4239 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1366,6 +1366,7 @@ "providers/dns/dnsimple", "providers/dns/dnsmadeeasy", "providers/dns/dnspod", + "providers/dns/dreamhost", "providers/dns/duckdns", "providers/dns/dyn", "providers/dns/exec", @@ -1397,7 +1398,7 @@ "providers/dns/vegadns", "providers/dns/vultr" ] - revision = "01c63ec08d1d85e3ad44c16dff95dadee26a81bc" + revision = "160d6fe60303699067faad57dc0b1e147ac499ef" [[projects]] branch = "master" diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md index c49664142..91e21ff26 100644 --- a/docs/configuration/acme.md +++ b/docs/configuration/acme.md @@ -263,6 +263,7 @@ Here is a list of supported `provider`s, that can automate the DNS verification, | [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | Not tested yet | | [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | Not tested yet | | [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet | +| [DreamHost](https://www.dreamhost.com/) | `dreamhost` | `DREAMHOST_API_KEY` | YES | | [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | Not tested yet | | [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet | | External Program | `exec` | `EXEC_PATH` | Not tested yet | diff --git a/vendor/github.com/xenolf/lego/acme/challenges.go b/vendor/github.com/xenolf/lego/acme/challenges.go index 1140b1073..d10f82b87 100644 --- a/vendor/github.com/xenolf/lego/acme/challenges.go +++ b/vendor/github.com/xenolf/lego/acme/challenges.go @@ -7,9 +7,11 @@ const ( // HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http // Note: HTTP01ChallengePath returns the URL path to fulfill this challenge HTTP01 = Challenge("http-01") + // DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns // Note: DNS01Record returns a DNS record which will fulfill this challenge DNS01 = Challenge("dns-01") + // TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01 TLSALPN01 = Challenge("tls-alpn-01") ) diff --git a/vendor/github.com/xenolf/lego/acme/client.go b/vendor/github.com/xenolf/lego/acme/client.go index 7af45033a..4908d592c 100644 --- a/vendor/github.com/xenolf/lego/acme/client.go +++ b/vendor/github.com/xenolf/lego/acme/client.go @@ -412,7 +412,7 @@ DNSNames: // the whole certificate will fail. func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) { if len(domains) == 0 { - return nil, errors.New("No domains to obtain a certificate for") + return nil, errors.New("no domains to obtain a certificate for") } if bundle { diff --git a/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go b/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go index 2836f213a..33c2065e0 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go +++ b/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go @@ -114,7 +114,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("cloudflare: %v", err) } - zoneID, err := d.client.ZoneIDByName(authZone) + zoneID, err := d.client.ZoneIDByName(acme.UnFqdn(authZone)) if err != nil { return fmt.Errorf("cloudflare: failed to find zone %s: %v", authZone, err) } @@ -149,7 +149,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("cloudflare: %v", err) } - zoneID, err := d.client.ZoneIDByName(authZone) + zoneID, err := d.client.ZoneIDByName(acme.UnFqdn(authZone)) if err != nil { return fmt.Errorf("cloudflare: failed to find zone %s: %v", authZone, err) } diff --git a/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go b/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go index 377346631..ff7a7d833 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go @@ -15,6 +15,7 @@ import ( "github.com/xenolf/lego/providers/dns/dnsimple" "github.com/xenolf/lego/providers/dns/dnsmadeeasy" "github.com/xenolf/lego/providers/dns/dnspod" + "github.com/xenolf/lego/providers/dns/dreamhost" "github.com/xenolf/lego/providers/dns/duckdns" "github.com/xenolf/lego/providers/dns/dyn" "github.com/xenolf/lego/providers/dns/exec" @@ -72,6 +73,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) return dnsmadeeasy.NewDNSProvider() case "dnspod": return dnspod.NewDNSProvider() + case "dreamhost": + return dreamhost.NewDNSProvider() case "duckdns": return duckdns.NewDNSProvider() case "dyn": diff --git a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go index 54e87dccb..ba0727b9e 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go @@ -38,11 +38,11 @@ type Client struct { // NewClient creates a DNSMadeEasy client func NewClient(apiKey string, apiSecret string) (*Client, error) { if apiKey == "" { - return nil, fmt.Errorf("DNSMadeEasy: credentials missing: API key") + return nil, fmt.Errorf("credentials missing: API key") } if apiSecret == "" { - return nil, fmt.Errorf("DNSMadeEasy: credentials missing: API secret") + return nil, fmt.Errorf("credentials missing: API secret") } return &Client{ diff --git a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go index c0f580d50..519a44519 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "strconv" "strings" "time" @@ -18,6 +17,7 @@ type Config struct { BaseURL string APIKey string APISecret string + Sandbox bool HTTPClient *http.Client PropagationTimeout time.Duration PollingInterval time.Duration @@ -55,15 +55,8 @@ func NewDNSProvider() (*DNSProvider, error) { return nil, fmt.Errorf("dnsmadeeasy: %v", err) } - var baseURL string - if sandbox, _ := strconv.ParseBool(env.GetOrFile("DNSMADEEASY_SANDBOX")); sandbox { - baseURL = "https://api.sandbox.dnsmadeeasy.com/V2.0" - } else { - baseURL = "https://api.dnsmadeeasy.com/V2.0" - } - config := NewDefaultConfig() - config.BaseURL = baseURL + config.Sandbox = env.GetOrDefaultBool("DNSMADEEASY_SANDBOX", false) config.APIKey = values["DNSMADEEASY_API_KEY"] config.APISecret = values["DNSMADEEASY_API_SECRET"] @@ -88,8 +81,15 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("dnsmadeeasy: the configuration of the DNS provider is nil") } - if config.BaseURL == "" { - return nil, fmt.Errorf("dnsmadeeasy: base URL missing") + var baseURL string + if config.Sandbox { + baseURL = "https://api.sandbox.dnsmadeeasy.com/V2.0" + } else { + if len(config.BaseURL) > 0 { + baseURL = config.BaseURL + } else { + baseURL = "https://api.dnsmadeeasy.com/V2.0" + } } client, err := NewClient(config.APIKey, config.APISecret) @@ -98,7 +98,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } client.HTTPClient = config.HTTPClient - client.BaseURL = config.BaseURL + client.BaseURL = baseURL return &DNSProvider{ client: client, diff --git a/vendor/github.com/xenolf/lego/providers/dns/dreamhost/client.go b/vendor/github.com/xenolf/lego/providers/dns/dreamhost/client.go new file mode 100644 index 000000000..640f9604e --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/dreamhost/client.go @@ -0,0 +1,73 @@ +package dreamhost + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/url" + + "github.com/xenolf/lego/log" +) + +const ( + defaultBaseURL = "https://api.dreamhost.com" + + cmdAddRecord = "dns-add_record" + cmdRemoveRecord = "dns-remove_record" +) + +type apiResponse struct { + Data string `json:"data"` + Result string `json:"result"` +} + +func (d *DNSProvider) buildQuery(action, domain, txt string) (*url.URL, error) { + u, err := url.Parse(d.config.BaseURL) + if err != nil { + return nil, err + } + + query := u.Query() + query.Set("key", d.config.APIKey) + query.Set("cmd", action) + query.Set("format", "json") + query.Set("record", domain) + query.Set("type", "TXT") + query.Set("value", txt) + query.Set("comment", url.QueryEscape("Managed By lego")) + u.RawQuery = query.Encode() + + return u, nil +} + +// updateTxtRecord will either add or remove a TXT record. +// action is either cmdAddRecord or cmdRemoveRecord +func (d *DNSProvider) updateTxtRecord(u fmt.Stringer) error { + resp, err := d.config.HTTPClient.Get(u.String()) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf("request failed with HTTP status code %d", resp.StatusCode) + } + + raw, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read body: %v", err) + } + + var response apiResponse + err = json.Unmarshal(raw, &response) + if err != nil { + return fmt.Errorf("unable to decode API server response: %v: %s", err, string(raw)) + } + + if response.Result == "error" { + return fmt.Errorf("add TXT record failed: %s", response.Data) + } + + log.Infof("dreamhost: %s", response.Data) + return nil +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go b/vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go new file mode 100644 index 000000000..cc0e36338 --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go @@ -0,0 +1,111 @@ +// Package dreamhost Adds lego support for http://dreamhost.com DNS updates +// See https://help.dreamhost.com/hc/en-us/articles/217560167-API_overview +// and https://help.dreamhost.com/hc/en-us/articles/217555707-DNS-API-commands for the API spec. +package dreamhost + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" +) + +// Config is used to configure the creation of the DNSProvider +type Config struct { + BaseURL string + APIKey string + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() *Config { + return &Config{ + BaseURL: defaultBaseURL, + PropagationTimeout: env.GetOrDefaultSecond("DREAMHOST_PROPAGATION_TIMEOUT", 60*time.Minute), + PollingInterval: env.GetOrDefaultSecond("DREAMHOST_POLLING_INTERVAL", 1*time.Minute), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond("DREAMHOST_HTTP_TIMEOUT", 30*time.Second), + }, + } +} + +// DNSProvider adds and removes the record for the DNS challenge +type DNSProvider struct { + config *Config +} + +// NewDNSProvider returns a new DNS provider using +// environment variable DREAMHOST_TOKEN for adding and removing the DNS record. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get("DREAMHOST_API_KEY") + if err != nil { + return nil, fmt.Errorf("dreamhost: %v", err) + } + + config := NewDefaultConfig() + config.APIKey = values["DREAMHOST_API_KEY"] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for DreamHost. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("dreamhost: the configuration of the DNS provider is nil") + } + + if config.APIKey == "" { + return nil, errors.New("dreamhost: credentials missing") + } + + if config.BaseURL == "" { + config.BaseURL = defaultBaseURL + } + + return &DNSProvider{config: config}, nil +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + record := acme.UnFqdn(fqdn) + + u, err := d.buildQuery(cmdAddRecord, record, value) + if err != nil { + return fmt.Errorf("dreamhost: %v", err) + } + + err = d.updateTxtRecord(u) + if err != nil { + return fmt.Errorf("dreamhost: %v", err) + } + return nil +} + +// CleanUp clears DreamHost TXT record +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + record := acme.UnFqdn(fqdn) + + u, err := d.buildQuery(cmdRemoveRecord, record, value) + if err != nil { + return fmt.Errorf("dreamhost: %v", err) + } + + err = d.updateTxtRecord(u) + if err != nil { + return fmt.Errorf("dreamhost: %v", err) + } + return nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go b/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go index 7fb6b3c0a..9ef6cb0ef 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go +++ b/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go @@ -77,7 +77,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } if config.ClientToken == "" || config.ClientSecret == "" || config.AccessToken == "" || config.Host == "" { - return nil, fmt.Errorf("FastDNS credentials are missing") + return nil, fmt.Errorf("fastdns: credentials are missing") } return &DNSProvider{config: config}, nil diff --git a/vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go b/vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go index 04e9fa196..0962d159c 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go +++ b/vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go @@ -102,6 +102,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { config.BaseURL = defaultBaseURL } + if config.TTL < minTTL { + return nil, fmt.Errorf("gandiv5: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + return &DNSProvider{ config: config, inProgressFQDNs: make(map[string]inProgressInfo), @@ -112,10 +116,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { fqdn, value, _ := acme.DNS01Record(domain, keyAuth) - if d.config.TTL < minTTL { - d.config.TTL = minTTL // 300 is gandi minimum value for ttl - } - // find authZone authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { diff --git a/vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go b/vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go index 69f61f772..4aab8744d 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go +++ b/vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go @@ -93,6 +93,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("glesys: incomplete credentials provided") } + if config.TTL < minTTL { + return nil, fmt.Errorf("glesys: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + return &DNSProvider{ config: config, activeRecords: make(map[string]int), @@ -103,9 +107,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { fqdn, value, _ := acme.DNS01Record(domain, keyAuth) - if d.config.TTL < minTTL { - d.config.TTL = minTTL // 60 is GleSYS minimum value for ttl - } // find authZone authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { diff --git a/vendor/github.com/xenolf/lego/providers/dns/nifcloud/client.go b/vendor/github.com/xenolf/lego/providers/dns/nifcloud/client.go index dc10cbc20..24a8f52b6 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/nifcloud/client.go +++ b/vendor/github.com/xenolf/lego/providers/dns/nifcloud/client.go @@ -89,13 +89,17 @@ type ChangeInfo struct { } // NewClient Creates a new client of NIFCLOUD DNS -func NewClient(accessKey string, secretKey string) *Client { +func NewClient(accessKey string, secretKey string) (*Client, error) { + if len(accessKey) == 0 || len(secretKey) == 0 { + return nil, errors.New("credentials missing") + } + return &Client{ accessKey: accessKey, secretKey: secretKey, BaseURL: defaultBaseURL, HTTPClient: &http.Client{}, - } + }, nil } // Client client of NIFCLOUD DNS diff --git a/vendor/github.com/xenolf/lego/providers/dns/nifcloud/nifcloud.go b/vendor/github.com/xenolf/lego/providers/dns/nifcloud/nifcloud.go index 6021cd51d..c8cf04845 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/nifcloud/nifcloud.go +++ b/vendor/github.com/xenolf/lego/providers/dns/nifcloud/nifcloud.go @@ -77,7 +77,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("nifcloud: the configuration of the DNS provider is nil") } - client := NewClient(config.AccessKey, config.SecretKey) + client, err := NewClient(config.AccessKey, config.SecretKey) + if err != nil { + return nil, fmt.Errorf("nifcloud: %v", err) + } if config.HTTPClient != nil { client.HTTPClient = config.HTTPClient diff --git a/vendor/github.com/xenolf/lego/providers/dns/otc/mock.go b/vendor/github.com/xenolf/lego/providers/dns/otc/mock.go deleted file mode 100644 index 127dd5cc7..000000000 --- a/vendor/github.com/xenolf/lego/providers/dns/otc/mock.go +++ /dev/null @@ -1,162 +0,0 @@ -package otc - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -var fakeOTCUserName = "test" -var fakeOTCPassword = "test" -var fakeOTCDomainName = "test" -var fakeOTCProjectName = "test" -var fakeOTCToken = "62244bc21da68d03ebac94e6636ff01f" - -// DNSMock mock -type DNSMock struct { - t *testing.T - Server *httptest.Server - Mux *http.ServeMux -} - -// NewDNSMock create a new DNSMock -func NewDNSMock(t *testing.T) *DNSMock { - return &DNSMock{ - t: t, - } -} - -// Setup creates the mock server -func (m *DNSMock) Setup() { - m.Mux = http.NewServeMux() - m.Server = httptest.NewServer(m.Mux) -} - -// ShutdownServer creates the mock server -func (m *DNSMock) ShutdownServer() { - m.Server.Close() -} - -// HandleAuthSuccessfully Handle auth successfully -func (m *DNSMock) HandleAuthSuccessfully() { - m.Mux.HandleFunc("/v3/auth/token", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Subject-Token", fakeOTCToken) - - fmt.Fprintf(w, `{ - "token": { - "catalog": [ - { - "type": "dns", - "id": "56cd81db1f8445d98652479afe07c5ba", - "name": "", - "endpoints": [ - { - "url": "%s", - "region": "eu-de", - "region_id": "eu-de", - "interface": "public", - "id": "0047a06690484d86afe04877074efddf" - } - ] - } - ] - }}`, m.Server.URL) - }) -} - -// HandleListZonesSuccessfully Handle list zones successfully -func (m *DNSMock) HandleListZonesSuccessfully() { - m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `{ - "zones":[{ - "id":"123123" - }]} - `) - - assert.Equal(m.t, r.Method, http.MethodGet) - assert.Equal(m.t, r.URL.Path, "/v2/zones") - assert.Equal(m.t, r.URL.RawQuery, "name=example.com.") - assert.Equal(m.t, r.Header.Get("Content-Type"), "application/json") - }) -} - -// HandleListZonesEmpty Handle list zones empty -func (m *DNSMock) HandleListZonesEmpty() { - m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `{ - "zones":[ - ]} - `) - - assert.Equal(m.t, r.Method, http.MethodGet) - assert.Equal(m.t, r.URL.Path, "/v2/zones") - assert.Equal(m.t, r.URL.RawQuery, "name=example.com.") - assert.Equal(m.t, r.Header.Get("Content-Type"), "application/json") - }) -} - -// HandleDeleteRecordsetsSuccessfully Handle delete recordsets successfully -func (m *DNSMock) HandleDeleteRecordsetsSuccessfully() { - m.Mux.HandleFunc("/v2/zones/123123/recordsets/321321", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `{ - "zones":[{ - "id":"123123" - }]} - `) - - assert.Equal(m.t, r.Method, http.MethodDelete) - assert.Equal(m.t, r.URL.Path, "/v2/zones/123123/recordsets/321321") - assert.Equal(m.t, r.Header.Get("Content-Type"), "application/json") - }) -} - -// HandleListRecordsetsEmpty Handle list recordsets empty -func (m *DNSMock) HandleListRecordsetsEmpty() { - m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `{ - "recordsets":[ - ]} - `) - - assert.Equal(m.t, r.URL.Path, "/v2/zones/123123/recordsets") - assert.Equal(m.t, r.URL.RawQuery, "type=TXT&name=_acme-challenge.example.com.") - }) -} - -// HandleListRecordsetsSuccessfully Handle list recordsets successfully -func (m *DNSMock) HandleListRecordsetsSuccessfully() { - m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - fmt.Fprintf(w, `{ - "recordsets":[{ - "id":"321321" - }]} - `) - - assert.Equal(m.t, r.URL.Path, "/v2/zones/123123/recordsets") - assert.Equal(m.t, r.URL.RawQuery, "type=TXT&name=_acme-challenge.example.com.") - - } else if r.Method == http.MethodPost { - body, err := ioutil.ReadAll(r.Body) - - assert.Nil(m.t, err) - exceptedString := "{\"name\":\"_acme-challenge.example.com.\",\"description\":\"Added TXT record for ACME dns-01 challenge using lego client\",\"type\":\"TXT\",\"ttl\":300,\"records\":[\"\\\"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI\\\"\"]}" - assert.Equal(m.t, string(body), exceptedString) - - fmt.Fprintf(w, `{ - "recordsets":[{ - "id":"321321" - }]} - `) - - } else { - m.t.Errorf("Expected method to be 'GET' or 'POST' but got '%s'", r.Method) - } - - assert.Equal(m.t, r.Header.Get("Content-Type"), "application/json") - }) -} diff --git a/vendor/github.com/xenolf/lego/providers/dns/rackspace/client.go b/vendor/github.com/xenolf/lego/providers/dns/rackspace/client.go index ce2032762..54b05c9db 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/rackspace/client.go +++ b/vendor/github.com/xenolf/lego/providers/dns/rackspace/client.go @@ -16,20 +16,44 @@ type AuthData struct { Auth `json:"auth"` } -// Identity Identity +// Identity Identity type Identity struct { - Access struct { - ServiceCatalog []struct { - Endpoints []struct { - PublicURL string `json:"publicURL"` - TenantID string `json:"tenantId"` - } `json:"endpoints"` - Name string `json:"name"` - } `json:"serviceCatalog"` - Token struct { - ID string `json:"id"` - } `json:"token"` - } `json:"access"` + Access Access `json:"access"` +} + +// Access Access +type Access struct { + ServiceCatalog []ServiceCatalog `json:"serviceCatalog"` + Token Token `json:"token"` +} + +// Token Token +type Token struct { + ID string `json:"id"` +} + +// ServiceCatalog ServiceCatalog +type ServiceCatalog struct { + Endpoints []Endpoint `json:"endpoints"` + Name string `json:"name"` +} + +// Endpoint Endpoint +type Endpoint struct { + PublicURL string `json:"publicURL"` + TenantID string `json:"tenantId"` +} + +// ZoneSearchResponse represents the response when querying Rackspace DNS zones +type ZoneSearchResponse struct { + TotalEntries int `json:"totalEntries"` + HostedZones []HostedZone `json:"domains"` +} + +// HostedZone HostedZone +type HostedZone struct { + ID int `json:"id"` + Name string `json:"name"` } // Records is the list of records sent/received from the DNS API diff --git a/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go b/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go index 2a35c907e..7b5e73d43 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go +++ b/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go @@ -89,39 +89,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("rackspace: credentials missing") } - authData := AuthData{ - Auth: Auth{ - APIKeyCredentials: APIKeyCredentials{ - Username: config.APIUser, - APIKey: config.APIKey, - }, - }, - } - - body, err := json.Marshal(authData) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, config.BaseURL, bytes.NewReader(body)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - - // client := &http.Client{Timeout: 30 * time.Second} - resp, err := config.HTTPClient.Do(req) - if err != nil { - return nil, fmt.Errorf("rackspace: error querying Identity API: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("rackspace: authentication failed: response code: %d", resp.StatusCode) - } - - var identity Identity - err = json.NewDecoder(resp.Body).Decode(&identity) + identity, err := login(config) if err != nil { return nil, fmt.Errorf("rackspace: %v", err) } @@ -134,6 +102,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { break } } + if dnsEndpoint == "" { return nil, fmt.Errorf("rackspace: failed to populate DNS endpoint, check Rackspace API for changes") } @@ -149,6 +118,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record to fulfill the dns-01 challenge func (d *DNSProvider) Present(domain, token, keyAuth string) error { fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + zoneID, err := d.getHostedZoneID(fqdn) if err != nil { return fmt.Errorf("rackspace: %v", err) @@ -178,6 +148,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { fqdn, _, _ := acme.DNS01Record(domain, keyAuth) + zoneID, err := d.getHostedZoneID(fqdn) if err != nil { return fmt.Errorf("rackspace: %v", err) @@ -204,15 +175,6 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // getHostedZoneID performs a lookup to get the DNS zone which needs // modifying for a given FQDN func (d *DNSProvider) getHostedZoneID(fqdn string) (int, error) { - // HostedZones represents the response when querying Rackspace DNS zones - type ZoneSearchResponse struct { - TotalEntries int `json:"totalEntries"` - HostedZones []struct { - ID int `json:"id"` - Name string `json:"name"` - } `json:"domains"` - } - authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return 0, err @@ -250,8 +212,7 @@ func (d *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*Record, error) { return nil, err } - recordsLength := len(records.Record) - switch recordsLength { + switch len(records.Record) { case 1: case 0: return nil, fmt.Errorf("no TXT record found for %s", fqdn) @@ -265,6 +226,7 @@ func (d *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*Record, error) { // makeRequest is a wrapper function used for making DNS API requests func (d *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) { url := d.cloudDNSEndpoint + uri + req, err := http.NewRequest(method, url, body) if err != nil { return nil, err @@ -292,3 +254,44 @@ func (d *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM return r, nil } + +func login(config *Config) (*Identity, error) { + authData := AuthData{ + Auth: Auth{ + APIKeyCredentials: APIKeyCredentials{ + Username: config.APIUser, + APIKey: config.APIKey, + }, + }, + } + + body, err := json.Marshal(authData) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, config.BaseURL, bytes.NewReader(body)) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := config.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("error querying Identity API: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("authentication failed: response code: %d", resp.StatusCode) + } + + var identity Identity + err = json.NewDecoder(resp.Body).Decode(&identity) + if err != nil { + return nil, err + } + + return &identity, nil +}