DNS challenge Cloudflare auth zone

This commit is contained in:
Ludovic Fernandez 2018-10-15 09:40:02 +02:00 committed by Traefiker Bot
parent 0335f6fba9
commit 7c2409b5a7
18 changed files with 312 additions and 248 deletions

3
Gopkg.lock generated
View file

@ -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"

View file

@ -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 |

View file

@ -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")
)

View file

@ -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 {

View file

@ -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)
}

View file

@ -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":

View file

@ -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{

View file

@ -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,

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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

View file

@ -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

View file

@ -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")
})
}

View file

@ -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

View file

@ -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
}