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.
This commit is contained in:
Brett Vickers 2016-03-17 13:59:15 -07:00
parent 43c55a690f
commit 47219adc00
18 changed files with 214 additions and 160 deletions

View file

@ -14,12 +14,12 @@ import (
"github.com/xenolf/lego/providers/dns/cloudflare" "github.com/xenolf/lego/providers/dns/cloudflare"
"github.com/xenolf/lego/providers/dns/digitalocean" "github.com/xenolf/lego/providers/dns/digitalocean"
"github.com/xenolf/lego/providers/dns/dnsimple" "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/gandi"
"github.com/xenolf/lego/providers/dns/googlecloud" "github.com/xenolf/lego/providers/dns/googlecloud"
"github.com/xenolf/lego/providers/dns/namecheap" "github.com/xenolf/lego/providers/dns/namecheap"
"github.com/xenolf/lego/providers/dns/rfc2136" "github.com/xenolf/lego/providers/dns/rfc2136"
"github.com/xenolf/lego/providers/dns/route53" "github.com/xenolf/lego/providers/dns/route53"
"github.com/xenolf/lego/providers/dns/dyn"
"github.com/xenolf/lego/providers/http/webroot" "github.com/xenolf/lego/providers/http/webroot"
) )
@ -89,38 +89,25 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
var provider acme.ChallengeProvider var provider acme.ChallengeProvider
switch c.GlobalString("dns") { switch c.GlobalString("dns") {
case "cloudflare": case "cloudflare":
provider, err = cloudflare.NewDNSProvider("", "") provider, err = cloudflare.NewDNSProvider()
case "digitalocean": case "digitalocean":
authToken := os.Getenv("DO_AUTH_TOKEN") provider, err = digitalocean.NewDNSProvider()
provider, err = digitalocean.NewDNSProvider(authToken)
case "dnsimple": case "dnsimple":
provider, err = dnsimple.NewDNSProvider("", "") 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)
case "dyn": case "dyn":
dynCustomerName := os.Getenv("DYN_CUSTOMER_NAME") provider, err = dyn.NewDNSProvider()
dynUserName := os.Getenv("DYN_USER_NAME") case "gandi":
dynPassword := os.Getenv("DYN_PASSWORD") provider, err = gandi.NewDNSProvider()
case "gcloud":
provider, err = dyn.NewDNSProvider(dynCustomerName, dynUserName, dynPassword) provider, err = googlecloud.NewDNSProvider()
case "manual": case "manual":
provider, err = acme.NewDNSProviderManual() 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 { if err != nil {

View file

@ -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 package cloudflare
import ( import (
@ -24,19 +25,25 @@ type DNSProvider struct {
authKey string authKey string
} }
// NewDNSProvider returns a DNSProvider instance with a configured cloudflare client. // NewDNSProvider returns a DNSProvider instance configured for cloudflare.
// Credentials can either be passed as arguments or through CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY env vars. // Credentials must be passed in the environment variables: CLOUDFLARE_EMAIL
func NewDNSProvider(cloudflareEmail, cloudflareKey string) (*DNSProvider, error) { // and CLOUDFLARE_API_KEY.
if cloudflareEmail == "" || cloudflareKey == "" { func NewDNSProvider() (*DNSProvider, error) {
cloudflareEmail, cloudflareKey = cloudflareEnvAuth() email := os.Getenv("CLOUDFLARE_EMAIL")
if cloudflareEmail == "" || cloudflareKey == "" { key := os.Getenv("CLOUDFLARE_API_KEY")
return nil, fmt.Errorf("CloudFlare credentials missing") 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{ return &DNSProvider{
authEmail: cloudflareEmail, authEmail: email,
authKey: cloudflareKey, authKey: key,
}, nil }, nil
} }
@ -192,15 +199,6 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM
return r.Result, nil 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 // cloudFlareRecord represents a CloudFlare DNS record
type cloudFlareRecord struct { type cloudFlareRecord struct {
Name string `json:"name"` Name string `json:"name"`

View file

@ -32,7 +32,7 @@ func restoreCloudFlareEnv() {
func TestNewDNSProviderValid(t *testing.T) { func TestNewDNSProviderValid(t *testing.T) {
os.Setenv("CLOUDFLARE_EMAIL", "") os.Setenv("CLOUDFLARE_EMAIL", "")
os.Setenv("CLOUDFLARE_API_KEY", "") os.Setenv("CLOUDFLARE_API_KEY", "")
_, err := NewDNSProvider("123", "123") _, err := NewDNSProviderCredentials("123", "123")
assert.NoError(t, err) assert.NoError(t, err)
restoreCloudFlareEnv() restoreCloudFlareEnv()
} }
@ -40,7 +40,7 @@ func TestNewDNSProviderValid(t *testing.T) {
func TestNewDNSProviderValidEnv(t *testing.T) { func TestNewDNSProviderValidEnv(t *testing.T) {
os.Setenv("CLOUDFLARE_EMAIL", "test@example.com") os.Setenv("CLOUDFLARE_EMAIL", "test@example.com")
os.Setenv("CLOUDFLARE_API_KEY", "123") os.Setenv("CLOUDFLARE_API_KEY", "123")
_, err := NewDNSProvider("", "") _, err := NewDNSProvider()
assert.NoError(t, err) assert.NoError(t, err)
restoreCloudFlareEnv() restoreCloudFlareEnv()
} }
@ -48,7 +48,7 @@ func TestNewDNSProviderValidEnv(t *testing.T) {
func TestNewDNSProviderMissingCredErr(t *testing.T) { func TestNewDNSProviderMissingCredErr(t *testing.T) {
os.Setenv("CLOUDFLARE_EMAIL", "") os.Setenv("CLOUDFLARE_EMAIL", "")
os.Setenv("CLOUDFLARE_API_KEY", "") os.Setenv("CLOUDFLARE_API_KEY", "")
_, err := NewDNSProvider("", "") _, err := NewDNSProvider()
assert.EqualError(t, err, "CloudFlare credentials missing") assert.EqualError(t, err, "CloudFlare credentials missing")
restoreCloudFlareEnv() restoreCloudFlareEnv()
} }
@ -58,7 +58,7 @@ func TestCloudFlarePresent(t *testing.T) {
t.Skip("skipping live test") t.Skip("skipping live test")
} }
provider, err := NewDNSProvider(cflareEmail, cflareAPIKey) provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey)
assert.NoError(t, err) assert.NoError(t, err)
err = provider.Present(cflareDomain, "", "123d==") err = provider.Present(cflareDomain, "", "123d==")
@ -72,7 +72,7 @@ func TestCloudFlareCleanUp(t *testing.T) {
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
provider, err := NewDNSProvider(cflareEmail, cflareAPIKey) provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey)
assert.NoError(t, err) assert.NoError(t, err)
err = provider.CleanUp(cflareDomain, "", "123d==") err = provider.CleanUp(cflareDomain, "", "123d==")

View file

@ -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 package digitalocean
import ( import (
@ -6,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os"
"sync" "sync"
"time" "time"
@ -20,10 +22,20 @@ type DNSProvider struct {
recordIDsMu sync.Mutex recordIDsMu sync.Mutex
} }
// NewDNSProvider returns a new DNSProvider instance. // NewDNSProvider returns a DNSProvider instance configured for Digital
// apiAuthToken is the personal access token created in the DigitalOcean account // Ocean. Credentials must be passed in the environment variable:
// control panel, and it will be sent in bearer authorization headers. // DO_AUTH_TOKEN.
func NewDNSProvider(apiAuthToken string) (*DNSProvider, error) { 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{ return &DNSProvider{
apiAuthToken: apiAuthToken, apiAuthToken: apiAuthToken,
recordIDs: make(map[string]int), recordIDs: make(map[string]int),

View file

@ -53,7 +53,7 @@ func TestDigitalOceanPresent(t *testing.T) {
defer mock.Close() defer mock.Close()
digitalOceanBaseURL = mock.URL digitalOceanBaseURL = mock.URL
doprov, err := NewDNSProvider(fakeDigitalOceanAuth) doprov, err := NewDNSProviderCredentials(fakeDigitalOceanAuth)
if doprov == nil { if doprov == nil {
t.Fatal("Expected non-nil DigitalOcean provider, but was nil") t.Fatal("Expected non-nil DigitalOcean provider, but was nil")
} }
@ -95,7 +95,7 @@ func TestDigitalOceanCleanUp(t *testing.T) {
defer mock.Close() defer mock.Close()
digitalOceanBaseURL = mock.URL digitalOceanBaseURL = mock.URL
doprov, err := NewDNSProvider(fakeDigitalOceanAuth) doprov, err := NewDNSProviderCredentials(fakeDigitalOceanAuth)
if doprov == nil { if doprov == nil {
t.Fatal("Expected non-nil DigitalOcean provider, but was nil") t.Fatal("Expected non-nil DigitalOcean provider, but was nil")
} }

View file

@ -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 package dnsimple
import ( import (
@ -15,22 +16,25 @@ type DNSProvider struct {
client *dnsimple.Client client *dnsimple.Client
} }
// NewDNSProvider returns a DNSProvider instance with a configured dnsimple client. // NewDNSProvider returns a DNSProvider instance configured for dnsimple.
// Authentication is either done using the passed credentials or - when empty - using the environment // Credentials must be passed in the environment variables: DNSIMPLE_EMAIL
// variables DNSIMPLE_EMAIL and DNSIMPLE_API_KEY. // and DNSIMPLE_API_KEY.
func NewDNSProvider(dnsimpleEmail, dnsimpleAPIKey string) (*DNSProvider, error) { func NewDNSProvider() (*DNSProvider, error) {
if dnsimpleEmail == "" || dnsimpleAPIKey == "" { email := os.Getenv("DNSIMPLE_EMAIL")
dnsimpleEmail, dnsimpleAPIKey = dnsimpleEnvAuth() key := os.Getenv("DNSIMPLE_API_KEY")
if dnsimpleEmail == "" || dnsimpleAPIKey == "" { 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") return nil, fmt.Errorf("DNSimple credentials missing")
} }
}
c := &DNSProvider{ return &DNSProvider{
client: dnsimple.NewClient(dnsimpleAPIKey, dnsimpleEmail), client: dnsimple.NewClient(key, email),
} }, nil
return c, nil
} }
// Present creates a TXT record to fulfil the dns-01 challenge. // 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 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
}

View file

@ -32,14 +32,14 @@ func restoreDNSimpleEnv() {
func TestNewDNSProviderValid(t *testing.T) { func TestNewDNSProviderValid(t *testing.T) {
os.Setenv("DNSIMPLE_EMAIL", "") os.Setenv("DNSIMPLE_EMAIL", "")
os.Setenv("DNSIMPLE_API_KEY", "") os.Setenv("DNSIMPLE_API_KEY", "")
_, err := NewDNSProvider("example@example.com", "123") _, err := NewDNSProviderCredentials("example@example.com", "123")
assert.NoError(t, err) assert.NoError(t, err)
restoreDNSimpleEnv() restoreDNSimpleEnv()
} }
func TestNewDNSProviderValidEnv(t *testing.T) { func TestNewDNSProviderValidEnv(t *testing.T) {
os.Setenv("DNSIMPLE_EMAIL", "example@example.com") os.Setenv("DNSIMPLE_EMAIL", "example@example.com")
os.Setenv("DNSIMPLE_API_KEY", "123") os.Setenv("DNSIMPLE_API_KEY", "123")
_, err := NewDNSProvider("", "") _, err := NewDNSProvider()
assert.NoError(t, err) assert.NoError(t, err)
restoreDNSimpleEnv() restoreDNSimpleEnv()
} }
@ -47,7 +47,7 @@ func TestNewDNSProviderValidEnv(t *testing.T) {
func TestNewDNSProviderMissingCredErr(t *testing.T) { func TestNewDNSProviderMissingCredErr(t *testing.T) {
os.Setenv("DNSIMPLE_EMAIL", "") os.Setenv("DNSIMPLE_EMAIL", "")
os.Setenv("DNSIMPLE_API_KEY", "") os.Setenv("DNSIMPLE_API_KEY", "")
_, err := NewDNSProvider("", "") _, err := NewDNSProvider()
assert.EqualError(t, err, "DNSimple credentials missing") assert.EqualError(t, err, "DNSimple credentials missing")
restoreDNSimpleEnv() restoreDNSimpleEnv()
} }
@ -57,7 +57,7 @@ func TestLiveDNSimplePresent(t *testing.T) {
t.Skip("skipping live test") t.Skip("skipping live test")
} }
provider, err := NewDNSProvider(dnsimpleEmail, dnsimpleAPIKey) provider, err := NewDNSProviderCredentials(dnsimpleEmail, dnsimpleAPIKey)
assert.NoError(t, err) assert.NoError(t, err)
err = provider.Present(dnsimpleDomain, "", "123d==") err = provider.Present(dnsimpleDomain, "", "123d==")
@ -71,7 +71,7 @@ func TestLiveDNSimpleCleanUp(t *testing.T) {
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
provider, err := NewDNSProvider(dnsimpleEmail, dnsimpleAPIKey) provider, err := NewDNSProviderCredentials(dnsimpleEmail, dnsimpleAPIKey)
assert.NoError(t, err) assert.NoError(t, err)
err = provider.CleanUp(dnsimpleDomain, "", "123d==") err = provider.CleanUp(dnsimpleDomain, "", "123d==")

View file

@ -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 package dyn
import ( import (
@ -6,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os"
"strconv" "strconv"
"time" "time"
@ -37,10 +39,23 @@ type DNSProvider struct {
token string token string
} }
// NewDNSProvider returns a new DNSProvider instance. customerName is // NewDNSProvider returns a DNSProvider instance configured for Dyn DNS.
// the customer name of the Dyn account. userName is the user name. password is // Credentials must be passed in the environment variables: DYN_CUSTOMER_NAME,
// the password. // DYN_USER_NAME and DYN_PASSWORD.
func NewDNSProvider(customerName, userName, password string) (*DNSProvider, error) { 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{ return &DNSProvider{
customerName: customerName, customerName: customerName,
userName: userName, userName: userName,

View file

@ -31,7 +31,7 @@ func TestLiveDynPresent(t *testing.T) {
t.Skip("skipping live test") t.Skip("skipping live test")
} }
provider, err := NewDNSProvider(dynCustomerName, dynUserName, dynPassword) provider, err := NewDNSProvider()
assert.NoError(t, err) assert.NoError(t, err)
err = provider.Present(dynDomain, "", "123d==") err = provider.Present(dynDomain, "", "123d==")
@ -45,7 +45,7 @@ func TestLiveDynCleanUp(t *testing.T) {
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
provider, err := NewDNSProvider(dynCustomerName, dynUserName, dynPassword) provider, err := NewDNSProvider()
assert.NoError(t, err) assert.NoError(t, err)
err = provider.CleanUp(dynDomain, "", "123d==") err = provider.CleanUp(dynDomain, "", "123d==")

View file

@ -9,6 +9,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -35,9 +36,16 @@ type DNSProvider struct {
inProgressMu sync.Mutex inProgressMu sync.Mutex
} }
// NewDNSProvider returns a new DNSProvider instance. apiKey is the // NewDNSProvider returns a DNSProvider instance configured for Gandi.
// API access key obtained from the Gandi account control panel. // Credentials must be passed in the environment variable: GANDI_API_KEY.
func NewDNSProvider(apiKey string) (*DNSProvider, error) { 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 == "" { if apiKey == "" {
return nil, fmt.Errorf("No Gandi API Key given") return nil, fmt.Errorf("No Gandi API Key given")
} }

View file

@ -1,4 +1,4 @@
package gandi_test package gandi
import ( import (
"io" "io"
@ -8,8 +8,6 @@ import (
"regexp" "regexp"
"strings" "strings"
"testing" "testing"
"github.com/xenolf/lego/providers/dns/gandi"
) )
// TestDNSProvider runs Present and CleanUp against a fake Gandi RPC // TestDNSProvider runs Present and CleanUp against a fake Gandi RPC
@ -17,7 +15,7 @@ import (
func TestDNSProvider(t *testing.T) { func TestDNSProvider(t *testing.T) {
fakeAPIKey := "123412341234123412341234" fakeAPIKey := "123412341234123412341234"
fakeKeyAuth := "XXXX" fakeKeyAuth := "XXXX"
provider, err := gandi.NewDNSProvider(fakeAPIKey) provider, err := NewDNSProviderCredentials(fakeAPIKey)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -47,11 +45,11 @@ func TestDNSProvider(t *testing.T) {
})) }))
defer fakeServer.Close() defer fakeServer.Close()
// override gandi endpoint to point to fake server // override gandi endpoint to point to fake server
savedEndpoint := gandi.Endpoint savedEndpoint := Endpoint
defer func() { defer func() {
gandi.Endpoint = savedEndpoint Endpoint = savedEndpoint
}() }()
gandi.Endpoint = fakeServer.URL + "/" Endpoint = fakeServer.URL + "/"
// run Present // run Present
err = provider.Present("abc.def.example.com", "", fakeKeyAuth) err = provider.Present("abc.def.example.com", "", fakeKeyAuth)
if err != nil { if err != nil {

View file

@ -22,12 +22,16 @@ type DNSProvider struct {
client *dns.Service client *dns.Service
} }
// NewDNSProvider returns a DNSProvider instance with a configured gcloud client. // NewDNSProvider returns a DNSProvider instance configured for Google Cloud
// Authentication is done using the local account credentials managed by the gcloud utility. // DNS. Credentials must be passed in the environment variable: GCE_PROJECT.
func NewDNSProvider(project string) (*DNSProvider, error) { func NewDNSProvider() (*DNSProvider, error) {
if project == "" { project := os.Getenv("GCE_PROJECT")
project = gcloudEnvAuth() 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 == "" { if project == "" {
return nil, fmt.Errorf("Google Cloud project name missing") 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 return found, nil
} }
func gcloudEnvAuth() (gcloud string) {
return os.Getenv("GCE_PROJECT")
}

View file

@ -36,7 +36,7 @@ func TestNewDNSProviderValid(t *testing.T) {
t.Skip("skipping live test (requires credentials)") t.Skip("skipping live test (requires credentials)")
} }
os.Setenv("GCE_PROJECT", "") os.Setenv("GCE_PROJECT", "")
_, err := NewDNSProvider("my-project") _, err := NewDNSProviderCredentials("my-project")
assert.NoError(t, err) assert.NoError(t, err)
restoreGCloudEnv() restoreGCloudEnv()
} }
@ -46,14 +46,14 @@ func TestNewDNSProviderValidEnv(t *testing.T) {
t.Skip("skipping live test (requires credentials)") t.Skip("skipping live test (requires credentials)")
} }
os.Setenv("GCE_PROJECT", "my-project") os.Setenv("GCE_PROJECT", "my-project")
_, err := NewDNSProvider("") _, err := NewDNSProvider()
assert.NoError(t, err) assert.NoError(t, err)
restoreGCloudEnv() restoreGCloudEnv()
} }
func TestNewDNSProviderMissingCredErr(t *testing.T) { func TestNewDNSProviderMissingCredErr(t *testing.T) {
os.Setenv("GCE_PROJECT", "") os.Setenv("GCE_PROJECT", "")
_, err := NewDNSProvider("") _, err := NewDNSProvider()
assert.EqualError(t, err, "Google Cloud project name missing") assert.EqualError(t, err, "Google Cloud project name missing")
restoreGCloudEnv() restoreGCloudEnv()
} }
@ -63,7 +63,7 @@ func TestLiveGoogleCloudPresent(t *testing.T) {
t.Skip("skipping live test") t.Skip("skipping live test")
} }
provider, err := NewDNSProvider(gcloudProject) provider, err := NewDNSProviderCredentials(gcloudProject)
assert.NoError(t, err) assert.NoError(t, err)
err = provider.Present(gcloudDomain, "", "123d==") err = provider.Present(gcloudDomain, "", "123d==")
@ -77,7 +77,7 @@ func TestLiveGoogleCloudCleanUp(t *testing.T) {
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
provider, err := NewDNSProvider(gcloudProject) provider, err := NewDNSProviderCredentials(gcloudProject)
assert.NoError(t, err) assert.NoError(t, err)
err = provider.CleanUp(gcloudDomain, "", "123d==") err = provider.CleanUp(gcloudDomain, "", "123d==")

View file

@ -44,16 +44,21 @@ type DNSProvider struct {
clientIP string clientIP string
} }
// NewDNSProvider returns a new DNSProvider instance. apiUser is the namecheap // NewDNSProvider returns a DNSProvider instance configured for namecheap.
// API user's account name, and apiKey is the account's API access key. // Credentials must be passed in the environment variables: NAMECHEAP_API_USER
func NewDNSProvider(apiUser, apiKey string) (*DNSProvider, error) { // and NAMECHEAP_API_KEY.
if apiUser == "" || apiKey == "" { func NewDNSProvider() (*DNSProvider, error) {
apiUser = os.Getenv("NAMECHEAP_API_USER") apiUser := os.Getenv("NAMECHEAP_API_USER")
apiKey = os.Getenv("NAMECHEAP_API_KEY") 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 == "" { if apiUser == "" || apiKey == "" {
return nil, fmt.Errorf("Namecheap credentials missing") return nil, fmt.Errorf("Namecheap credentials missing")
} }
}
clientIP, err := getClientIP() clientIP, err := getClientIP()
if err != nil { if err != nil {
@ -68,8 +73,9 @@ func NewDNSProvider(apiUser, apiKey string) (*DNSProvider, error) {
}, nil }, nil
} }
// Timeout : Namecheap can sometimes take a long time to complete an update, so wait // Timeout returns the timeout and interval to use when checking for DNS
// up to 60 minutes for the update to propagate. // 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) { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return 60 * time.Minute, 15 * time.Second return 60 * time.Minute, 15 * time.Second
} }

View file

@ -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 package rfc2136
import ( import (
"fmt" "fmt"
"net" "net"
"os"
"strings" "strings"
"time" "time"
@ -20,10 +22,29 @@ type DNSProvider struct {
tsigSecret string tsigSecret string
} }
// NewDNSProvider returns a new DNSProvider instance. // NewDNSProvider returns a DNSProvider instance configured for rfc2136
// To disable TSIG authentication 'tsigAlgorithm, 'tsigKey' and 'tsigSecret' must be set to the empty string. // dynamic update. Credentials must be passed in the environment variables:
// 'nameserver' must be a network address in the the form "host" or "host:port". // RFC2136_NAMESERVER, RFC2136_TSIG_ALGORITHM, RFC2136_TSIG_KEY and
func NewDNSProvider(nameserver, tsigAlgorithm, tsigKey, tsigSecret string) (*DNSProvider, error) { // 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. // Append the default DNS port if none is specified.
if _, _, err := net.SplitHostPort(nameserver); err != nil { if _, _, err := net.SplitHostPort(nameserver); err != nil {
if strings.Contains(err.Error(), "missing port") { if strings.Contains(err.Error(), "missing port") {

View file

@ -61,9 +61,9 @@ func TestRFC2136ServerSuccess(t *testing.T) {
} }
defer server.Shutdown() defer server.Shutdown()
provider, err := NewDNSProvider(addrstr, "", "", "") provider, err := NewDNSProviderCredentials(addrstr, "", "", "")
if err != nil { 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 { if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil {
t.Errorf("Expected Present() to return no error but the error was -> %v", err) 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() defer server.Shutdown()
provider, err := NewDNSProvider(addrstr, "", "", "") provider, err := NewDNSProviderCredentials(addrstr, "", "", "")
if err != nil { 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 { if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err == nil {
t.Errorf("Expected Present() to return an error but it did not.") t.Errorf("Expected Present() to return an error but it did not.")
@ -103,9 +103,9 @@ func TestRFC2136TsigClient(t *testing.T) {
} }
defer server.Shutdown() defer server.Shutdown()
provider, err := NewDNSProvider(addrstr, "", rfc2136TestTsigKey, rfc2136TestTsigSecret) provider, err := NewDNSProviderCredentials(addrstr, "", rfc2136TestTsigKey, rfc2136TestTsigSecret)
if err != nil { 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 { if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil {
t.Errorf("Expected Present() to return no error but the error was -> %v", err) 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) t.Fatalf("Error packing expect msg: %v", err)
} }
provider, err := NewDNSProvider(addrstr, "", "", "") provider, err := NewDNSProviderCredentials(addrstr, "", "", "")
if err != nil { 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 { if err := provider.Present(rfc2136TestDomain, "", "1234d=="); err != nil {

View file

@ -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 package route53
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
"time" "time"
@ -16,25 +18,35 @@ type DNSProvider struct {
client *route53.Route53 client *route53.Route53
} }
// NewDNSProvider returns a DNSProvider instance with a configured route53 client. // NewDNSProvider returns a DNSProvider instance configured for the AWS
// Authentication is either done using the passed credentials or - when empty - falling back to // route53 service. The AWS region name must be passed in the environment
// the customary AWS credential mechanisms, including the file referenced by $AWS_CREDENTIAL_FILE // variable AWS_REGION.
// (defaulting to $HOME/.aws/credentials) optionally scoped to $AWS_PROFILE, credentials func NewDNSProvider() (*DNSProvider, error) {
// supplied by the environment variables AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY [ + AWS_SECURITY_TOKEN ], regionName := os.Getenv("AWS_REGION")
// and finally credentials available via the EC2 instance metadata service. return NewDNSProviderCredentials("", "", regionName)
func NewDNSProvider(awsAccessKey, awsSecretKey, awsRegionName string) (*DNSProvider, error) { }
region, ok := aws.Regions[awsRegionName]
// 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 { 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: // 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_PROFILE / AWS_CREDENTIAL_FILE, if provided
// - uses AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY and optionally AWS_SECURITY_TOKEN, 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 // - uses EC2 instance metadata credentials (http://169.254.169.254/latest/meta-data/…), if available
// ...and otherwise returns an error // ...and otherwise returns an error
auth, err := aws.GetAuth(awsAccessKey, awsSecretKey) auth, err := aws.GetAuth(accessKey, secretKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -100,7 +100,8 @@ func makeRoute53Provider(server *testutil.HTTPServer) *DNSProvider {
func TestNewDNSProviderValid(t *testing.T) { func TestNewDNSProviderValid(t *testing.T) {
os.Setenv("AWS_ACCESS_KEY_ID", "") os.Setenv("AWS_ACCESS_KEY_ID", "")
os.Setenv("AWS_SECRET_ACCESS_KEY", "") 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) assert.NoError(t, err)
restoreRoute53Env() restoreRoute53Env()
} }
@ -108,7 +109,8 @@ func TestNewDNSProviderValid(t *testing.T) {
func TestNewDNSProviderValidEnv(t *testing.T) { func TestNewDNSProviderValidEnv(t *testing.T) {
os.Setenv("AWS_ACCESS_KEY_ID", "123") os.Setenv("AWS_ACCESS_KEY_ID", "123")
os.Setenv("AWS_SECRET_ACCESS_KEY", "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) assert.NoError(t, err)
restoreRoute53Env() restoreRoute53Env()
} }
@ -124,7 +126,7 @@ func TestNewDNSProviderMissingAuthErr(t *testing.T) {
awsClient := aws.RetryingClient awsClient := aws.RetryingClient
aws.RetryingClient = &http.Client{Timeout: time.Millisecond} 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") assert.EqualError(t, err, "No valid AWS authentication found")
restoreRoute53Env() restoreRoute53Env()
@ -133,7 +135,7 @@ func TestNewDNSProviderMissingAuthErr(t *testing.T) {
} }
func TestNewDNSProviderInvalidRegionErr(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") assert.EqualError(t, err, "Invalid AWS region name us-east-3")
} }