From 47219adc008ae430baeb16761d0d751e3ae45267 Mon Sep 17 00:00:00 2001 From: Brett Vickers Date: Thu, 17 Mar 2016 13:59:15 -0700 Subject: [PATCH] Make DNS provider credential-handling more consistent. Different DNS providers were handling credentials in different ways. Some were reading credential environment variables in cli_handlers.go and then passing them into the NewDNSProvider function, while others were reading the environment variables within their NewDNSProvider functions. This change replaces each DNS challenge's NewDNSProvider function with two new functions: (1) a NewDNSProvider function that takes no parameters and uses the environment to read credentials, and (2) a NewDNSProviderCredentials that takes credentials as parameters. --- cli_handlers.go | 43 +++++++------------ providers/dns/cloudflare/cloudflare.go | 38 ++++++++-------- providers/dns/cloudflare/cloudflare_test.go | 10 ++--- providers/dns/digitalocean/digitalocean.go | 22 +++++++--- .../dns/digitalocean/digitalocean_test.go | 4 +- providers/dns/dnsimple/dnsimple.go | 43 ++++++++----------- providers/dns/dnsimple/dnsimple_test.go | 10 ++--- providers/dns/dyn/dyn.go | 25 ++++++++--- providers/dns/dyn/dyn_test.go | 4 +- providers/dns/gandi/gandi.go | 14 ++++-- providers/dns/gandi/gandi_test.go | 12 +++--- providers/dns/googlecloud/googlecloud.go | 20 ++++----- providers/dns/googlecloud/googlecloud_test.go | 10 ++--- providers/dns/namecheap/namecheap.go | 26 ++++++----- providers/dns/rfc2136/rfc2136.go | 31 ++++++++++--- providers/dns/rfc2136/rfc2136_test.go | 16 +++---- providers/dns/route53/route53.go | 36 ++++++++++------ providers/dns/route53/route53_test.go | 10 +++-- 18 files changed, 214 insertions(+), 160 deletions(-) diff --git a/cli_handlers.go b/cli_handlers.go index eea75ede..fe51438d 100644 --- a/cli_handlers.go +++ b/cli_handlers.go @@ -14,12 +14,12 @@ import ( "github.com/xenolf/lego/providers/dns/cloudflare" "github.com/xenolf/lego/providers/dns/digitalocean" "github.com/xenolf/lego/providers/dns/dnsimple" + "github.com/xenolf/lego/providers/dns/dyn" "github.com/xenolf/lego/providers/dns/gandi" "github.com/xenolf/lego/providers/dns/googlecloud" "github.com/xenolf/lego/providers/dns/namecheap" "github.com/xenolf/lego/providers/dns/rfc2136" "github.com/xenolf/lego/providers/dns/route53" - "github.com/xenolf/lego/providers/dns/dyn" "github.com/xenolf/lego/providers/http/webroot" ) @@ -89,38 +89,25 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) { var provider acme.ChallengeProvider switch c.GlobalString("dns") { case "cloudflare": - provider, err = cloudflare.NewDNSProvider("", "") + provider, err = cloudflare.NewDNSProvider() case "digitalocean": - authToken := os.Getenv("DO_AUTH_TOKEN") - - provider, err = digitalocean.NewDNSProvider(authToken) + provider, err = digitalocean.NewDNSProvider() case "dnsimple": - provider, err = dnsimple.NewDNSProvider("", "") - case "gandi": - apiKey := os.Getenv("GANDI_API_KEY") - provider, err = gandi.NewDNSProvider(apiKey) - case "gcloud": - provider, err = googlecloud.NewDNSProvider("") - case "namecheap": - provider, err = namecheap.NewDNSProvider("", "") - case "route53": - awsRegion := os.Getenv("AWS_REGION") - provider, err = route53.NewDNSProvider("", "", awsRegion) - case "rfc2136": - nameserver := os.Getenv("RFC2136_NAMESERVER") - tsigAlgorithm := os.Getenv("RFC2136_TSIG_ALGORITHM") - tsigKey := os.Getenv("RFC2136_TSIG_KEY") - tsigSecret := os.Getenv("RFC2136_TSIG_SECRET") - - provider, err = rfc2136.NewDNSProvider(nameserver, tsigAlgorithm, tsigKey, tsigSecret) + provider, err = dnsimple.NewDNSProvider() case "dyn": - dynCustomerName := os.Getenv("DYN_CUSTOMER_NAME") - dynUserName := os.Getenv("DYN_USER_NAME") - dynPassword := os.Getenv("DYN_PASSWORD") - - provider, err = dyn.NewDNSProvider(dynCustomerName, dynUserName, dynPassword) + provider, err = dyn.NewDNSProvider() + case "gandi": + provider, err = gandi.NewDNSProvider() + case "gcloud": + provider, err = googlecloud.NewDNSProvider() case "manual": provider, err = acme.NewDNSProviderManual() + case "namecheap": + provider, err = namecheap.NewDNSProvider() + case "route53": + provider, err = route53.NewDNSProvider() + case "rfc2136": + provider, err = rfc2136.NewDNSProvider() } if err != nil { diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index 307cc4ef..d0067e22 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -1,4 +1,5 @@ -// Package cloudflare implements a DNS provider for solving the DNS-01 challenge using cloudflare DNS. +// Package cloudflare implements a DNS provider for solving the DNS-01 +// challenge using cloudflare DNS. package cloudflare import ( @@ -24,19 +25,25 @@ type DNSProvider struct { authKey string } -// NewDNSProvider returns a DNSProvider instance with a configured cloudflare client. -// Credentials can either be passed as arguments or through CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY env vars. -func NewDNSProvider(cloudflareEmail, cloudflareKey string) (*DNSProvider, error) { - if cloudflareEmail == "" || cloudflareKey == "" { - cloudflareEmail, cloudflareKey = cloudflareEnvAuth() - if cloudflareEmail == "" || cloudflareKey == "" { - return nil, fmt.Errorf("CloudFlare credentials missing") - } +// NewDNSProvider returns a DNSProvider instance configured for cloudflare. +// Credentials must be passed in the environment variables: CLOUDFLARE_EMAIL +// and CLOUDFLARE_API_KEY. +func NewDNSProvider() (*DNSProvider, error) { + email := os.Getenv("CLOUDFLARE_EMAIL") + key := os.Getenv("CLOUDFLARE_API_KEY") + return NewDNSProviderCredentials(email, key) +} + +// NewDNSProviderCredentials uses the supplied credentials to return a +// DNSProvider instance configured for cloudflare. +func NewDNSProviderCredentials(email, key string) (*DNSProvider, error) { + if email == "" || key == "" { + return nil, fmt.Errorf("CloudFlare credentials missing") } return &DNSProvider{ - authEmail: cloudflareEmail, - authKey: cloudflareKey, + authEmail: email, + authKey: key, }, nil } @@ -192,15 +199,6 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM return r.Result, nil } -func cloudflareEnvAuth() (email, apiKey string) { - email = os.Getenv("CLOUDFLARE_EMAIL") - apiKey = os.Getenv("CLOUDFLARE_API_KEY") - if len(email) == 0 || len(apiKey) == 0 { - return "", "" - } - return -} - // cloudFlareRecord represents a CloudFlare DNS record type cloudFlareRecord struct { Name string `json:"name"` diff --git a/providers/dns/cloudflare/cloudflare_test.go b/providers/dns/cloudflare/cloudflare_test.go index 63936ce6..19b5a40b 100644 --- a/providers/dns/cloudflare/cloudflare_test.go +++ b/providers/dns/cloudflare/cloudflare_test.go @@ -32,7 +32,7 @@ func restoreCloudFlareEnv() { func TestNewDNSProviderValid(t *testing.T) { os.Setenv("CLOUDFLARE_EMAIL", "") os.Setenv("CLOUDFLARE_API_KEY", "") - _, err := NewDNSProvider("123", "123") + _, err := NewDNSProviderCredentials("123", "123") assert.NoError(t, err) restoreCloudFlareEnv() } @@ -40,7 +40,7 @@ func TestNewDNSProviderValid(t *testing.T) { func TestNewDNSProviderValidEnv(t *testing.T) { os.Setenv("CLOUDFLARE_EMAIL", "test@example.com") os.Setenv("CLOUDFLARE_API_KEY", "123") - _, err := NewDNSProvider("", "") + _, err := NewDNSProvider() assert.NoError(t, err) restoreCloudFlareEnv() } @@ -48,7 +48,7 @@ func TestNewDNSProviderValidEnv(t *testing.T) { func TestNewDNSProviderMissingCredErr(t *testing.T) { os.Setenv("CLOUDFLARE_EMAIL", "") os.Setenv("CLOUDFLARE_API_KEY", "") - _, err := NewDNSProvider("", "") + _, err := NewDNSProvider() assert.EqualError(t, err, "CloudFlare credentials missing") restoreCloudFlareEnv() } @@ -58,7 +58,7 @@ func TestCloudFlarePresent(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProvider(cflareEmail, cflareAPIKey) + provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey) assert.NoError(t, err) err = provider.Present(cflareDomain, "", "123d==") @@ -72,7 +72,7 @@ func TestCloudFlareCleanUp(t *testing.T) { time.Sleep(time.Second * 2) - provider, err := NewDNSProvider(cflareEmail, cflareAPIKey) + provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey) assert.NoError(t, err) err = provider.CleanUp(cflareDomain, "", "123d==") diff --git a/providers/dns/digitalocean/digitalocean.go b/providers/dns/digitalocean/digitalocean.go index 5aeaf11d..152cf81e 100644 --- a/providers/dns/digitalocean/digitalocean.go +++ b/providers/dns/digitalocean/digitalocean.go @@ -1,4 +1,5 @@ -// Package digitalocean implements a DNS provider for solving the DNS-01 challenge using digitalocean DNS. +// Package digitalocean implements a DNS provider for solving the DNS-01 +// challenge using digitalocean DNS. package digitalocean import ( @@ -6,6 +7,7 @@ import ( "encoding/json" "fmt" "net/http" + "os" "sync" "time" @@ -20,10 +22,20 @@ type DNSProvider struct { recordIDsMu sync.Mutex } -// NewDNSProvider returns a new DNSProvider instance. -// apiAuthToken is the personal access token created in the DigitalOcean account -// control panel, and it will be sent in bearer authorization headers. -func NewDNSProvider(apiAuthToken string) (*DNSProvider, error) { +// NewDNSProvider returns a DNSProvider instance configured for Digital +// Ocean. Credentials must be passed in the environment variable: +// DO_AUTH_TOKEN. +func NewDNSProvider() (*DNSProvider, error) { + apiAuthToken := os.Getenv("DO_AUTH_TOKEN") + return NewDNSProviderCredentials(apiAuthToken) +} + +// NewDNSProviderCredentials uses the supplied credentials to return a +// DNSProvider instance configured for Digital Ocean. +func NewDNSProviderCredentials(apiAuthToken string) (*DNSProvider, error) { + if apiAuthToken == "" { + return nil, fmt.Errorf("DigitalOcean credentials missing") + } return &DNSProvider{ apiAuthToken: apiAuthToken, recordIDs: make(map[string]int), diff --git a/providers/dns/digitalocean/digitalocean_test.go b/providers/dns/digitalocean/digitalocean_test.go index 8fc383a0..7498508b 100644 --- a/providers/dns/digitalocean/digitalocean_test.go +++ b/providers/dns/digitalocean/digitalocean_test.go @@ -53,7 +53,7 @@ func TestDigitalOceanPresent(t *testing.T) { defer mock.Close() digitalOceanBaseURL = mock.URL - doprov, err := NewDNSProvider(fakeDigitalOceanAuth) + doprov, err := NewDNSProviderCredentials(fakeDigitalOceanAuth) if doprov == nil { t.Fatal("Expected non-nil DigitalOcean provider, but was nil") } @@ -95,7 +95,7 @@ func TestDigitalOceanCleanUp(t *testing.T) { defer mock.Close() digitalOceanBaseURL = mock.URL - doprov, err := NewDNSProvider(fakeDigitalOceanAuth) + doprov, err := NewDNSProviderCredentials(fakeDigitalOceanAuth) if doprov == nil { t.Fatal("Expected non-nil DigitalOcean provider, but was nil") } diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go index cde77298..bb66935d 100644 --- a/providers/dns/dnsimple/dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -1,4 +1,5 @@ -// Package dnsimple implements a DNS provider for solving the DNS-01 challenge using dnsimple DNS. +// Package dnsimple implements a DNS provider for solving the DNS-01 challenge +// using dnsimple DNS. package dnsimple import ( @@ -15,22 +16,25 @@ type DNSProvider struct { client *dnsimple.Client } -// NewDNSProvider returns a DNSProvider instance with a configured dnsimple client. -// Authentication is either done using the passed credentials or - when empty - using the environment -// variables DNSIMPLE_EMAIL and DNSIMPLE_API_KEY. -func NewDNSProvider(dnsimpleEmail, dnsimpleAPIKey string) (*DNSProvider, error) { - if dnsimpleEmail == "" || dnsimpleAPIKey == "" { - dnsimpleEmail, dnsimpleAPIKey = dnsimpleEnvAuth() - if dnsimpleEmail == "" || dnsimpleAPIKey == "" { - return nil, fmt.Errorf("DNSimple credentials missing") - } +// NewDNSProvider returns a DNSProvider instance configured for dnsimple. +// Credentials must be passed in the environment variables: DNSIMPLE_EMAIL +// and DNSIMPLE_API_KEY. +func NewDNSProvider() (*DNSProvider, error) { + email := os.Getenv("DNSIMPLE_EMAIL") + key := os.Getenv("DNSIMPLE_API_KEY") + return NewDNSProviderCredentials(email, key) +} + +// NewDNSProviderCredentials uses the supplied credentials to return a +// DNSProvider instance configured for dnsimple. +func NewDNSProviderCredentials(email, key string) (*DNSProvider, error) { + if email == "" || key == "" { + return nil, fmt.Errorf("DNSimple credentials missing") } - c := &DNSProvider{ - client: dnsimple.NewClient(dnsimpleAPIKey, dnsimpleEmail), - } - - return c, nil + return &DNSProvider{ + client: dnsimple.NewClient(key, email), + }, nil } // Present creates a TXT record to fulfil the dns-01 challenge. @@ -130,12 +134,3 @@ func (c *DNSProvider) extractRecordName(fqdn, domain string) string { } return name } - -func dnsimpleEnvAuth() (email, apiKey string) { - email = os.Getenv("DNSIMPLE_EMAIL") - apiKey = os.Getenv("DNSIMPLE_API_KEY") - if len(email) == 0 || len(apiKey) == 0 { - return "", "" - } - return -} diff --git a/providers/dns/dnsimple/dnsimple_test.go b/providers/dns/dnsimple/dnsimple_test.go index 7cb19fea..4926b3df 100644 --- a/providers/dns/dnsimple/dnsimple_test.go +++ b/providers/dns/dnsimple/dnsimple_test.go @@ -32,14 +32,14 @@ func restoreDNSimpleEnv() { func TestNewDNSProviderValid(t *testing.T) { os.Setenv("DNSIMPLE_EMAIL", "") os.Setenv("DNSIMPLE_API_KEY", "") - _, err := NewDNSProvider("example@example.com", "123") + _, err := NewDNSProviderCredentials("example@example.com", "123") assert.NoError(t, err) restoreDNSimpleEnv() } func TestNewDNSProviderValidEnv(t *testing.T) { os.Setenv("DNSIMPLE_EMAIL", "example@example.com") os.Setenv("DNSIMPLE_API_KEY", "123") - _, err := NewDNSProvider("", "") + _, err := NewDNSProvider() assert.NoError(t, err) restoreDNSimpleEnv() } @@ -47,7 +47,7 @@ func TestNewDNSProviderValidEnv(t *testing.T) { func TestNewDNSProviderMissingCredErr(t *testing.T) { os.Setenv("DNSIMPLE_EMAIL", "") os.Setenv("DNSIMPLE_API_KEY", "") - _, err := NewDNSProvider("", "") + _, err := NewDNSProvider() assert.EqualError(t, err, "DNSimple credentials missing") restoreDNSimpleEnv() } @@ -57,7 +57,7 @@ func TestLiveDNSimplePresent(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProvider(dnsimpleEmail, dnsimpleAPIKey) + provider, err := NewDNSProviderCredentials(dnsimpleEmail, dnsimpleAPIKey) assert.NoError(t, err) err = provider.Present(dnsimpleDomain, "", "123d==") @@ -71,7 +71,7 @@ func TestLiveDNSimpleCleanUp(t *testing.T) { time.Sleep(time.Second * 1) - provider, err := NewDNSProvider(dnsimpleEmail, dnsimpleAPIKey) + provider, err := NewDNSProviderCredentials(dnsimpleEmail, dnsimpleAPIKey) assert.NoError(t, err) err = provider.CleanUp(dnsimpleDomain, "", "123d==") diff --git a/providers/dns/dyn/dyn.go b/providers/dns/dyn/dyn.go index a88a6e7a..86daf96c 100644 --- a/providers/dns/dyn/dyn.go +++ b/providers/dns/dyn/dyn.go @@ -1,4 +1,5 @@ -// Package dyn implements a DNS provider for solving the DNS-01 challenge using Dyn Managed DNS. +// Package dyn implements a DNS provider for solving the DNS-01 challenge +// using Dyn Managed DNS. package dyn import ( @@ -6,6 +7,7 @@ import ( "encoding/json" "fmt" "net/http" + "os" "strconv" "time" @@ -37,10 +39,23 @@ type DNSProvider struct { token string } -// NewDNSProvider returns a new DNSProvider instance. customerName is -// the customer name of the Dyn account. userName is the user name. password is -// the password. -func NewDNSProvider(customerName, userName, password string) (*DNSProvider, error) { +// NewDNSProvider returns a DNSProvider instance configured for Dyn DNS. +// Credentials must be passed in the environment variables: DYN_CUSTOMER_NAME, +// DYN_USER_NAME and DYN_PASSWORD. +func NewDNSProvider() (*DNSProvider, error) { + customerName := os.Getenv("DYN_CUSTOMER_NAME") + userName := os.Getenv("DYN_USER_NAME") + password := os.Getenv("DYN_PASSWORD") + return NewDNSProviderCredentials(customerName, userName, password) +} + +// NewDNSProviderCredentials uses the supplied credentials to return a +// DNSProvider instance configured for Dyn DNS. +func NewDNSProviderCredentials(customerName, userName, password string) (*DNSProvider, error) { + if customerName == "" || userName == "" || password == "" { + return nil, fmt.Errorf("DynDNS credentials missing") + } + return &DNSProvider{ customerName: customerName, userName: userName, diff --git a/providers/dns/dyn/dyn_test.go b/providers/dns/dyn/dyn_test.go index fcc3d63e..0d28d5d0 100644 --- a/providers/dns/dyn/dyn_test.go +++ b/providers/dns/dyn/dyn_test.go @@ -31,7 +31,7 @@ func TestLiveDynPresent(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProvider(dynCustomerName, dynUserName, dynPassword) + provider, err := NewDNSProvider() assert.NoError(t, err) err = provider.Present(dynDomain, "", "123d==") @@ -45,7 +45,7 @@ func TestLiveDynCleanUp(t *testing.T) { time.Sleep(time.Second * 1) - provider, err := NewDNSProvider(dynCustomerName, dynUserName, dynPassword) + provider, err := NewDNSProvider() assert.NoError(t, err) err = provider.CleanUp(dynDomain, "", "123d==") diff --git a/providers/dns/gandi/gandi.go b/providers/dns/gandi/gandi.go index fa1f88e6..014a085b 100644 --- a/providers/dns/gandi/gandi.go +++ b/providers/dns/gandi/gandi.go @@ -9,6 +9,7 @@ import ( "io" "io/ioutil" "net/http" + "os" "strings" "sync" "time" @@ -35,9 +36,16 @@ type DNSProvider struct { inProgressMu sync.Mutex } -// NewDNSProvider returns a new DNSProvider instance. apiKey is the -// API access key obtained from the Gandi account control panel. -func NewDNSProvider(apiKey string) (*DNSProvider, error) { +// NewDNSProvider returns a DNSProvider instance configured for Gandi. +// Credentials must be passed in the environment variable: GANDI_API_KEY. +func NewDNSProvider() (*DNSProvider, error) { + apiKey := os.Getenv("GANDI_API_KEY") + return NewDNSProviderCredentials(apiKey) +} + +// NewDNSProviderCredentials uses the supplied credentials to return a +// DNSProvider instance configured for Gandi. +func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) { if apiKey == "" { return nil, fmt.Errorf("No Gandi API Key given") } diff --git a/providers/dns/gandi/gandi_test.go b/providers/dns/gandi/gandi_test.go index 1f44a962..6b3b8d74 100644 --- a/providers/dns/gandi/gandi_test.go +++ b/providers/dns/gandi/gandi_test.go @@ -1,4 +1,4 @@ -package gandi_test +package gandi import ( "io" @@ -8,8 +8,6 @@ import ( "regexp" "strings" "testing" - - "github.com/xenolf/lego/providers/dns/gandi" ) // TestDNSProvider runs Present and CleanUp against a fake Gandi RPC @@ -17,7 +15,7 @@ import ( func TestDNSProvider(t *testing.T) { fakeAPIKey := "123412341234123412341234" fakeKeyAuth := "XXXX" - provider, err := gandi.NewDNSProvider(fakeAPIKey) + provider, err := NewDNSProviderCredentials(fakeAPIKey) if err != nil { t.Fatal(err) } @@ -47,11 +45,11 @@ func TestDNSProvider(t *testing.T) { })) defer fakeServer.Close() // override gandi endpoint to point to fake server - savedEndpoint := gandi.Endpoint + savedEndpoint := Endpoint defer func() { - gandi.Endpoint = savedEndpoint + Endpoint = savedEndpoint }() - gandi.Endpoint = fakeServer.URL + "/" + Endpoint = fakeServer.URL + "/" // run Present err = provider.Present("abc.def.example.com", "", fakeKeyAuth) if err != nil { diff --git a/providers/dns/googlecloud/googlecloud.go b/providers/dns/googlecloud/googlecloud.go index ace3500f..f5026056 100644 --- a/providers/dns/googlecloud/googlecloud.go +++ b/providers/dns/googlecloud/googlecloud.go @@ -22,12 +22,16 @@ type DNSProvider struct { client *dns.Service } -// NewDNSProvider returns a DNSProvider instance with a configured gcloud client. -// Authentication is done using the local account credentials managed by the gcloud utility. -func NewDNSProvider(project string) (*DNSProvider, error) { - if project == "" { - project = gcloudEnvAuth() - } +// NewDNSProvider returns a DNSProvider instance configured for Google Cloud +// DNS. Credentials must be passed in the environment variable: GCE_PROJECT. +func NewDNSProvider() (*DNSProvider, error) { + project := os.Getenv("GCE_PROJECT") + return NewDNSProviderCredentials(project) +} + +// NewDNSProviderCredentials uses the supplied credentials to return a +// DNSProvider instance configured for Google Cloud DNS. +func NewDNSProviderCredentials(project string) (*DNSProvider, error) { if project == "" { return nil, fmt.Errorf("Google Cloud project name missing") } @@ -149,7 +153,3 @@ func (c *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSe return found, nil } - -func gcloudEnvAuth() (gcloud string) { - return os.Getenv("GCE_PROJECT") -} diff --git a/providers/dns/googlecloud/googlecloud_test.go b/providers/dns/googlecloud/googlecloud_test.go index 156ad773..d7378816 100644 --- a/providers/dns/googlecloud/googlecloud_test.go +++ b/providers/dns/googlecloud/googlecloud_test.go @@ -36,7 +36,7 @@ func TestNewDNSProviderValid(t *testing.T) { t.Skip("skipping live test (requires credentials)") } os.Setenv("GCE_PROJECT", "") - _, err := NewDNSProvider("my-project") + _, err := NewDNSProviderCredentials("my-project") assert.NoError(t, err) restoreGCloudEnv() } @@ -46,14 +46,14 @@ func TestNewDNSProviderValidEnv(t *testing.T) { t.Skip("skipping live test (requires credentials)") } os.Setenv("GCE_PROJECT", "my-project") - _, err := NewDNSProvider("") + _, err := NewDNSProvider() assert.NoError(t, err) restoreGCloudEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { os.Setenv("GCE_PROJECT", "") - _, err := NewDNSProvider("") + _, err := NewDNSProvider() assert.EqualError(t, err, "Google Cloud project name missing") restoreGCloudEnv() } @@ -63,7 +63,7 @@ func TestLiveGoogleCloudPresent(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProvider(gcloudProject) + provider, err := NewDNSProviderCredentials(gcloudProject) assert.NoError(t, err) err = provider.Present(gcloudDomain, "", "123d==") @@ -77,7 +77,7 @@ func TestLiveGoogleCloudCleanUp(t *testing.T) { time.Sleep(time.Second * 1) - provider, err := NewDNSProvider(gcloudProject) + provider, err := NewDNSProviderCredentials(gcloudProject) assert.NoError(t, err) err = provider.CleanUp(gcloudDomain, "", "123d==") diff --git a/providers/dns/namecheap/namecheap.go b/providers/dns/namecheap/namecheap.go index dea75b65..d7eb4093 100644 --- a/providers/dns/namecheap/namecheap.go +++ b/providers/dns/namecheap/namecheap.go @@ -44,15 +44,20 @@ type DNSProvider struct { clientIP string } -// NewDNSProvider returns a new DNSProvider instance. apiUser is the namecheap -// API user's account name, and apiKey is the account's API access key. -func NewDNSProvider(apiUser, apiKey string) (*DNSProvider, error) { +// NewDNSProvider returns a DNSProvider instance configured for namecheap. +// Credentials must be passed in the environment variables: NAMECHEAP_API_USER +// and NAMECHEAP_API_KEY. +func NewDNSProvider() (*DNSProvider, error) { + apiUser := os.Getenv("NAMECHEAP_API_USER") + apiKey := os.Getenv("NAMECHEAP_API_KEY") + return NewDNSProviderCredentials(apiUser, apiKey) +} + +// NewDNSProviderCredentials uses the supplied credentials to return a +// DNSProvider instance configured for namecheap. +func NewDNSProviderCredentials(apiUser, apiKey string) (*DNSProvider, error) { if apiUser == "" || apiKey == "" { - apiUser = os.Getenv("NAMECHEAP_API_USER") - apiKey = os.Getenv("NAMECHEAP_API_KEY") - if apiUser == "" || apiKey == "" { - return nil, fmt.Errorf("Namecheap credentials missing") - } + return nil, fmt.Errorf("Namecheap credentials missing") } clientIP, err := getClientIP() @@ -68,8 +73,9 @@ func NewDNSProvider(apiUser, apiKey string) (*DNSProvider, error) { }, nil } -// Timeout : Namecheap can sometimes take a long time to complete an update, so wait -// up to 60 minutes for the update to propagate. +// Timeout returns the timeout and interval to use when checking for DNS +// propagation. Namecheap can sometimes take a long time to complete an +// update, so wait up to 60 minutes for the update to propagate. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return 60 * time.Minute, 15 * time.Second } diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go index 3d32409b..9f597aa2 100644 --- a/providers/dns/rfc2136/rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -1,9 +1,11 @@ -// Package rfc2136 implements a DNS provider for solving the DNS-01 challenge using the rfc2136 dynamic update. +// Package rfc2136 implements a DNS provider for solving the DNS-01 challenge +// using the rfc2136 dynamic update. package rfc2136 import ( "fmt" "net" + "os" "strings" "time" @@ -20,10 +22,29 @@ type DNSProvider struct { tsigSecret string } -// NewDNSProvider returns a new DNSProvider instance. -// To disable TSIG authentication 'tsigAlgorithm, 'tsigKey' and 'tsigSecret' must be set to the empty string. -// 'nameserver' must be a network address in the the form "host" or "host:port". -func NewDNSProvider(nameserver, tsigAlgorithm, tsigKey, tsigSecret string) (*DNSProvider, error) { +// NewDNSProvider returns a DNSProvider instance configured for rfc2136 +// dynamic update. Credentials must be passed in the environment variables: +// RFC2136_NAMESERVER, RFC2136_TSIG_ALGORITHM, RFC2136_TSIG_KEY and +// RFC2136_TSIG_SECRET. To disable TSIG authentication, leave the TSIG +// variables unset. RFC2136_NAMESERVER must be a network address in the form +// "host" or "host:port". +func NewDNSProvider() (*DNSProvider, error) { + nameserver := os.Getenv("RFC2136_NAMESERVER") + tsigAlgorithm := os.Getenv("RFC2136_TSIG_ALGORITHM") + tsigKey := os.Getenv("RFC2136_TSIG_KEY") + tsigSecret := os.Getenv("RFC2136_TSIG_SECRET") + return NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret) +} + +// NewDNSProviderCredentials uses the supplied credentials to return a +// DNSProvider instance configured for rfc2136 dynamic update. To disable TSIG +// authentication, leave the TSIG parameters as empty strings. +// nameserver must be a network address in the form "host" or "host:port". +func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret string) (*DNSProvider, error) { + if nameserver == "" { + return nil, fmt.Errorf("RFC2136 nameserver missing") + } + // Append the default DNS port if none is specified. if _, _, err := net.SplitHostPort(nameserver); err != nil { if strings.Contains(err.Error(), "missing port") { diff --git a/providers/dns/rfc2136/rfc2136_test.go b/providers/dns/rfc2136/rfc2136_test.go index 2aa8aa22..a2515e99 100644 --- a/providers/dns/rfc2136/rfc2136_test.go +++ b/providers/dns/rfc2136/rfc2136_test.go @@ -61,9 +61,9 @@ func TestRFC2136ServerSuccess(t *testing.T) { } defer server.Shutdown() - provider, err := NewDNSProvider(addrstr, "", "", "") + provider, err := NewDNSProviderCredentials(addrstr, "", "", "") if err != nil { - t.Fatalf("Expected NewDNSProvider() to return no error but the error was -> %v", err) + t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err) } if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil { t.Errorf("Expected Present() to return no error but the error was -> %v", err) @@ -81,9 +81,9 @@ func TestRFC2136ServerError(t *testing.T) { } defer server.Shutdown() - provider, err := NewDNSProvider(addrstr, "", "", "") + provider, err := NewDNSProviderCredentials(addrstr, "", "", "") if err != nil { - t.Fatalf("Expected NewDNSProvider() to return no error but the error was -> %v", err) + t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err) } if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err == nil { t.Errorf("Expected Present() to return an error but it did not.") @@ -103,9 +103,9 @@ func TestRFC2136TsigClient(t *testing.T) { } defer server.Shutdown() - provider, err := NewDNSProvider(addrstr, "", rfc2136TestTsigKey, rfc2136TestTsigSecret) + provider, err := NewDNSProviderCredentials(addrstr, "", rfc2136TestTsigKey, rfc2136TestTsigSecret) if err != nil { - t.Fatalf("Expected NewDNSProvider() to return no error but the error was -> %v", err) + t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err) } if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil { t.Errorf("Expected Present() to return no error but the error was -> %v", err) @@ -135,9 +135,9 @@ func TestRFC2136ValidUpdatePacket(t *testing.T) { t.Fatalf("Error packing expect msg: %v", err) } - provider, err := NewDNSProvider(addrstr, "", "", "") + provider, err := NewDNSProviderCredentials(addrstr, "", "", "") if err != nil { - t.Fatalf("Expected NewDNSProvider() to return no error but the error was -> %v", err) + t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err) } if err := provider.Present(rfc2136TestDomain, "", "1234d=="); err != nil { diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index eb1ffdf3..7e9e131d 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -1,8 +1,10 @@ -// Package route53 implements a DNS provider for solving the DNS-01 challenge using route53 DNS. +// Package route53 implements a DNS provider for solving the DNS-01 challenge +// using route53 DNS. package route53 import ( "fmt" + "os" "strings" "time" @@ -16,25 +18,35 @@ type DNSProvider struct { client *route53.Route53 } -// NewDNSProvider returns a DNSProvider instance with a configured route53 client. -// Authentication is either done using the passed credentials or - when empty - falling back to -// the customary AWS credential mechanisms, including the file referenced by $AWS_CREDENTIAL_FILE -// (defaulting to $HOME/.aws/credentials) optionally scoped to $AWS_PROFILE, credentials -// supplied by the environment variables AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY [ + AWS_SECURITY_TOKEN ], -// and finally credentials available via the EC2 instance metadata service. -func NewDNSProvider(awsAccessKey, awsSecretKey, awsRegionName string) (*DNSProvider, error) { - region, ok := aws.Regions[awsRegionName] +// NewDNSProvider returns a DNSProvider instance configured for the AWS +// route53 service. The AWS region name must be passed in the environment +// variable AWS_REGION. +func NewDNSProvider() (*DNSProvider, error) { + regionName := os.Getenv("AWS_REGION") + return NewDNSProviderCredentials("", "", regionName) +} + +// NewDNSProviderCredentials uses the supplied credentials to return a +// DNSProvider instance configured for the AWS route53 service. Authentication +// is done using the passed credentials or, if empty, falling back to the +// custonmary AWS credential mechanisms, including the file referenced by +// $AWS_CREDENTIAL_FILE (defaulting to $HOME/.aws/credentials) optionally +// scoped to $AWS_PROFILE, credentials supplied by the environment variables +// AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY [ + AWS_SECURITY_TOKEN ], and +// finally credentials available via the EC2 instance metadata service. +func NewDNSProviderCredentials(accessKey, secretKey, regionName string) (*DNSProvider, error) { + region, ok := aws.Regions[regionName] if !ok { - return nil, fmt.Errorf("Invalid AWS region name %s", awsRegionName) + return nil, fmt.Errorf("Invalid AWS region name %s", regionName) } // use aws.GetAuth, which tries really hard to find credentails: - // - uses awsAccessKey and awsSecretKey, if provided + // - uses accessKey and secretKey, if provided // - uses AWS_PROFILE / AWS_CREDENTIAL_FILE, if provided // - uses AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY and optionally AWS_SECURITY_TOKEN, if provided // - uses EC2 instance metadata credentials (http://169.254.169.254/latest/meta-data/…), if available // ...and otherwise returns an error - auth, err := aws.GetAuth(awsAccessKey, awsSecretKey) + auth, err := aws.GetAuth(accessKey, secretKey) if err != nil { return nil, err } diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go index 8bb675c3..2b23f960 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -100,7 +100,8 @@ func makeRoute53Provider(server *testutil.HTTPServer) *DNSProvider { func TestNewDNSProviderValid(t *testing.T) { os.Setenv("AWS_ACCESS_KEY_ID", "") os.Setenv("AWS_SECRET_ACCESS_KEY", "") - _, err := NewDNSProvider("123", "123", "us-east-1") + os.Setenv("AWS_REGION", "") + _, err := NewDNSProviderCredentials("123", "123", "us-east-1") assert.NoError(t, err) restoreRoute53Env() } @@ -108,7 +109,8 @@ func TestNewDNSProviderValid(t *testing.T) { func TestNewDNSProviderValidEnv(t *testing.T) { os.Setenv("AWS_ACCESS_KEY_ID", "123") os.Setenv("AWS_SECRET_ACCESS_KEY", "123") - _, err := NewDNSProvider("", "", "us-east-1") + os.Setenv("AWS_REGION", "us-east-1") + _, err := NewDNSProvider() assert.NoError(t, err) restoreRoute53Env() } @@ -124,7 +126,7 @@ func TestNewDNSProviderMissingAuthErr(t *testing.T) { awsClient := aws.RetryingClient aws.RetryingClient = &http.Client{Timeout: time.Millisecond} - _, err := NewDNSProvider("", "", "us-east-1") + _, err := NewDNSProviderCredentials("", "", "us-east-1") assert.EqualError(t, err, "No valid AWS authentication found") restoreRoute53Env() @@ -133,7 +135,7 @@ func TestNewDNSProviderMissingAuthErr(t *testing.T) { } func TestNewDNSProviderInvalidRegionErr(t *testing.T) { - _, err := NewDNSProvider("123", "123", "us-east-3") + _, err := NewDNSProviderCredentials("123", "123", "us-east-3") assert.EqualError(t, err, "Invalid AWS region name us-east-3") }