Merge pull request #144 from xenolf/refactor-providers
Move DNS providers out of ACME package.
This commit is contained in:
commit
06fc07007f
15 changed files with 208 additions and 178 deletions
|
@ -69,7 +69,7 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
|
||||||
|
|
||||||
logf("[INFO][%s] Checking DNS record propagation...", domain)
|
logf("[INFO][%s] Checking DNS record propagation...", domain)
|
||||||
|
|
||||||
err = waitFor(30, 2, func() (bool, error) {
|
err = WaitFor(60*time.Second, 2*time.Second, func() (bool, error) {
|
||||||
return preCheckDNS(fqdn, value)
|
return preCheckDNS(fqdn, value)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -160,7 +160,7 @@ func dnsQuery(fqdn string, rtype uint16, nameserver string, recursive bool) (in
|
||||||
func lookupNameservers(fqdn string) ([]string, error) {
|
func lookupNameservers(fqdn string) ([]string, error) {
|
||||||
var authoritativeNss []string
|
var authoritativeNss []string
|
||||||
|
|
||||||
zone, err := findZoneByFqdn(fqdn, recursiveNameserver)
|
zone, err := FindZoneByFqdn(fqdn, recursiveNameserver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -182,8 +182,8 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
||||||
return nil, fmt.Errorf("Could not determine authoritative nameservers")
|
return nil, fmt.Errorf("Could not determine authoritative nameservers")
|
||||||
}
|
}
|
||||||
|
|
||||||
// findZoneByFqdn determines the zone of the given fqdn
|
// FindZoneByFqdn determines the zone of the given fqdn
|
||||||
func findZoneByFqdn(fqdn, nameserver string) (string, error) {
|
func FindZoneByFqdn(fqdn, nameserver string) (string, error) {
|
||||||
// Do we have it cached?
|
// Do we have it cached?
|
||||||
if zone, ok := fqdnToZone[fqdn]; ok {
|
if zone, ok := fqdnToZone[fqdn]; ok {
|
||||||
return zone, nil
|
return zone, nil
|
||||||
|
@ -208,8 +208,8 @@ func findZoneByFqdn(fqdn, nameserver string) (string, error) {
|
||||||
if soa, ok := ans.(*dns.SOA); ok {
|
if soa, ok := ans.(*dns.SOA); ok {
|
||||||
zone := soa.Hdr.Name
|
zone := soa.Hdr.Name
|
||||||
// If we ended up on one of the TLDs, it means the domain did not exist.
|
// If we ended up on one of the TLDs, it means the domain did not exist.
|
||||||
publicsuffix, _ := publicsuffix.PublicSuffix(unFqdn(zone))
|
publicsuffix, _ := publicsuffix.PublicSuffix(UnFqdn(zone))
|
||||||
if publicsuffix == unFqdn(zone) {
|
if publicsuffix == UnFqdn(zone) {
|
||||||
return "", fmt.Errorf("Could not determine zone authoritatively")
|
return "", fmt.Errorf("Could not determine zone authoritatively")
|
||||||
}
|
}
|
||||||
fqdnToZone[fqdn] = zone
|
fqdnToZone[fqdn] = zone
|
||||||
|
@ -223,8 +223,8 @@ func findZoneByFqdn(fqdn, nameserver string) (string, error) {
|
||||||
if soa, ok := ns.(*dns.SOA); ok {
|
if soa, ok := ns.(*dns.SOA); ok {
|
||||||
zone := soa.Hdr.Name
|
zone := soa.Hdr.Name
|
||||||
// If we ended up on one of the TLDs, it means the domain did not exist.
|
// If we ended up on one of the TLDs, it means the domain did not exist.
|
||||||
publicsuffix, _ := publicsuffix.PublicSuffix(unFqdn(zone))
|
publicsuffix, _ := publicsuffix.PublicSuffix(UnFqdn(zone))
|
||||||
if publicsuffix == unFqdn(zone) {
|
if publicsuffix == UnFqdn(zone) {
|
||||||
return "", fmt.Errorf("Could not determine zone authoritatively")
|
return "", fmt.Errorf("Could not determine zone authoritatively")
|
||||||
}
|
}
|
||||||
fqdnToZone[fqdn] = zone
|
fqdnToZone[fqdn] = zone
|
||||||
|
@ -234,8 +234,13 @@ func findZoneByFqdn(fqdn, nameserver string) (string, error) {
|
||||||
return "", fmt.Errorf("NS %s did not return the expected SOA record in the authority section", nameserver)
|
return "", fmt.Errorf("NS %s did not return the expected SOA record in the authority section", nameserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// toFqdn converts the name into a fqdn appending a trailing dot.
|
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
||||||
func toFqdn(name string) string {
|
func ClearFqdnCache() {
|
||||||
|
fqdnToZone = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFqdn converts the name into a fqdn appending a trailing dot.
|
||||||
|
func ToFqdn(name string) string {
|
||||||
n := len(name)
|
n := len(name)
|
||||||
if n == 0 || name[n-1] == '.' {
|
if n == 0 || name[n-1] == '.' {
|
||||||
return name
|
return name
|
||||||
|
@ -243,39 +248,11 @@ func toFqdn(name string) string {
|
||||||
return name + "."
|
return name + "."
|
||||||
}
|
}
|
||||||
|
|
||||||
// unFqdn converts the fqdn into a name removing the trailing dot.
|
// UnFqdn converts the fqdn into a name removing the trailing dot.
|
||||||
func unFqdn(name string) string {
|
func UnFqdn(name string) string {
|
||||||
n := len(name)
|
n := len(name)
|
||||||
if n != 0 && name[n-1] == '.' {
|
if n != 0 && name[n-1] == '.' {
|
||||||
return name[:n-1]
|
return name[:n-1]
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// clearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
|
||||||
func clearFqdnCache() {
|
|
||||||
fqdnToZone = map[string]string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitFor polls the given function 'f', once every 'interval' seconds, up to 'timeout' seconds.
|
|
||||||
func waitFor(timeout, interval int, f func() (bool, error)) error {
|
|
||||||
var lastErr string
|
|
||||||
timeup := time.After(time.Duration(timeout) * time.Second)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timeup:
|
|
||||||
return fmt.Errorf("Time limit exceeded. Last error: %s", lastErr)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
stop, err := f()
|
|
||||||
if stop {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Duration(interval) * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -163,23 +163,3 @@ func TestCheckAuthoritativeNssErr(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitForTimeout(t *testing.T) {
|
|
||||||
c := make(chan error)
|
|
||||||
go func() {
|
|
||||||
err := waitFor(3, 1, func() (bool, error) {
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
c <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
timeout := time.After(4 * time.Second)
|
|
||||||
select {
|
|
||||||
case <-timeout:
|
|
||||||
t.Fatal("timeout exceeded")
|
|
||||||
case err := <-c:
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("expected timeout error; got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
29
acme/utils.go
Normal file
29
acme/utils.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WaitFor polls the given function 'f', once every 'interval', up to 'timeout'.
|
||||||
|
func WaitFor(timeout, interval time.Duration, f func() (bool, error)) error {
|
||||||
|
var lastErr string
|
||||||
|
timeup := time.After(timeout)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timeup:
|
||||||
|
return fmt.Errorf("Time limit exceeded. Last error: %s", lastErr)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
stop, err := f()
|
||||||
|
if stop {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(interval)
|
||||||
|
}
|
||||||
|
}
|
26
acme/utils_test.go
Normal file
26
acme/utils_test.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWaitForTimeout(t *testing.T) {
|
||||||
|
c := make(chan error)
|
||||||
|
go func() {
|
||||||
|
err := WaitFor(3*time.Second, 1*time.Second, func() (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
c <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
timeout := time.After(4 * time.Second)
|
||||||
|
select {
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatal("timeout exceeded")
|
||||||
|
case err := <-c:
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected timeout error; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,11 @@ import (
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acme"
|
||||||
|
"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/rfc2136"
|
||||||
|
"github.com/xenolf/lego/providers/dns/route53"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkFolder(path string) error {
|
func checkFolder(path string) error {
|
||||||
|
@ -67,23 +72,23 @@ 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 = acme.NewDNSProviderCloudFlare("", "")
|
provider, err = cloudflare.NewDNSProvider("", "")
|
||||||
case "digitalocean":
|
case "digitalocean":
|
||||||
authToken := os.Getenv("DO_AUTH_TOKEN")
|
authToken := os.Getenv("DO_AUTH_TOKEN")
|
||||||
|
|
||||||
provider, err = acme.NewDNSProviderDigitalOcean(authToken)
|
provider, err = digitalocean.NewDNSProvider(authToken)
|
||||||
case "dnsimple":
|
case "dnsimple":
|
||||||
provider, err = acme.NewDNSProviderDNSimple("", "")
|
provider, err = dnsimple.NewDNSProvider("", "")
|
||||||
case "route53":
|
case "route53":
|
||||||
awsRegion := os.Getenv("AWS_REGION")
|
awsRegion := os.Getenv("AWS_REGION")
|
||||||
provider, err = acme.NewDNSProviderRoute53("", "", awsRegion)
|
provider, err = route53.NewDNSProvider("", "", awsRegion)
|
||||||
case "rfc2136":
|
case "rfc2136":
|
||||||
nameserver := os.Getenv("RFC2136_NAMESERVER")
|
nameserver := os.Getenv("RFC2136_NAMESERVER")
|
||||||
tsigAlgorithm := os.Getenv("RFC2136_TSIG_ALGORITHM")
|
tsigAlgorithm := os.Getenv("RFC2136_TSIG_ALGORITHM")
|
||||||
tsigKey := os.Getenv("RFC2136_TSIG_KEY")
|
tsigKey := os.Getenv("RFC2136_TSIG_KEY")
|
||||||
tsigSecret := os.Getenv("RFC2136_TSIG_SECRET")
|
tsigSecret := os.Getenv("RFC2136_TSIG_SECRET")
|
||||||
|
|
||||||
provider, err = acme.NewDNSProviderRFC2136(nameserver, tsigAlgorithm, tsigKey, tsigSecret)
|
provider, err = rfc2136.NewDNSProvider(nameserver, tsigAlgorithm, tsigKey, tsigSecret)
|
||||||
case "manual":
|
case "manual":
|
||||||
provider, err = acme.NewDNSProviderManual()
|
provider, err = acme.NewDNSProviderManual()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
package acme
|
// Package cloudflare implements a DNS provider for solving the DNS-01 challenge using cloudflare DNS.
|
||||||
|
package cloudflare
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -9,21 +10,23 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CloudFlareAPIURL represents the API endpoint to call.
|
// CloudFlareAPIURL represents the API endpoint to call.
|
||||||
// TODO: Unexport?
|
// TODO: Unexport?
|
||||||
const CloudFlareAPIURL = "https://api.cloudflare.com/client/v4"
|
const CloudFlareAPIURL = "https://api.cloudflare.com/client/v4"
|
||||||
|
|
||||||
// DNSProviderCloudFlare is an implementation of the DNSProvider interface
|
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||||
type DNSProviderCloudFlare struct {
|
type DNSProvider struct {
|
||||||
authEmail string
|
authEmail string
|
||||||
authKey string
|
authKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDNSProviderCloudFlare returns a DNSProviderCloudFlare instance with a configured cloudflare client.
|
// 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.
|
// Credentials can either be passed as arguments or through CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY env vars.
|
||||||
func NewDNSProviderCloudFlare(cloudflareEmail, cloudflareKey string) (*DNSProviderCloudFlare, error) {
|
func NewDNSProvider(cloudflareEmail, cloudflareKey string) (*DNSProvider, error) {
|
||||||
if cloudflareEmail == "" || cloudflareKey == "" {
|
if cloudflareEmail == "" || cloudflareKey == "" {
|
||||||
cloudflareEmail, cloudflareKey = cloudflareEnvAuth()
|
cloudflareEmail, cloudflareKey = cloudflareEnvAuth()
|
||||||
if cloudflareEmail == "" || cloudflareKey == "" {
|
if cloudflareEmail == "" || cloudflareKey == "" {
|
||||||
|
@ -31,15 +34,15 @@ func NewDNSProviderCloudFlare(cloudflareEmail, cloudflareKey string) (*DNSProvid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &DNSProviderCloudFlare{
|
return &DNSProvider{
|
||||||
authEmail: cloudflareEmail,
|
authEmail: cloudflareEmail,
|
||||||
authKey: cloudflareKey,
|
authKey: cloudflareKey,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present creates a TXT record to fulfil the dns-01 challenge
|
// Present creates a TXT record to fulfil the dns-01 challenge
|
||||||
func (c *DNSProviderCloudFlare) Present(domain, token, keyAuth string) error {
|
func (c *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
fqdn, value, _ := DNS01Record(domain, keyAuth)
|
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||||
zoneID, err := c.getHostedZoneID(fqdn)
|
zoneID, err := c.getHostedZoneID(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -47,7 +50,7 @@ func (c *DNSProviderCloudFlare) Present(domain, token, keyAuth string) error {
|
||||||
|
|
||||||
rec := cloudFlareRecord{
|
rec := cloudFlareRecord{
|
||||||
Type: "TXT",
|
Type: "TXT",
|
||||||
Name: unFqdn(fqdn),
|
Name: acme.UnFqdn(fqdn),
|
||||||
Content: value,
|
Content: value,
|
||||||
TTL: 120,
|
TTL: 120,
|
||||||
}
|
}
|
||||||
|
@ -66,8 +69,8 @@ func (c *DNSProviderCloudFlare) Present(domain, token, keyAuth string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp removes the TXT record matching the specified parameters
|
// CleanUp removes the TXT record matching the specified parameters
|
||||||
func (c *DNSProviderCloudFlare) CleanUp(domain, token, keyAuth string) error {
|
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
fqdn, _, _ := DNS01Record(domain, keyAuth)
|
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||||
|
|
||||||
record, err := c.findTxtRecord(fqdn)
|
record, err := c.findTxtRecord(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -82,7 +85,7 @@ func (c *DNSProviderCloudFlare) CleanUp(domain, token, keyAuth string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSProviderCloudFlare) getHostedZoneID(fqdn string) (string, error) {
|
func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
|
||||||
// HostedZone represents a CloudFlare DNS zone
|
// HostedZone represents a CloudFlare DNS zone
|
||||||
type HostedZone struct {
|
type HostedZone struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -102,7 +105,7 @@ func (c *DNSProviderCloudFlare) getHostedZoneID(fqdn string) (string, error) {
|
||||||
|
|
||||||
var hostedZone HostedZone
|
var hostedZone HostedZone
|
||||||
for _, zone := range zones {
|
for _, zone := range zones {
|
||||||
name := toFqdn(zone.Name)
|
name := acme.ToFqdn(zone.Name)
|
||||||
if strings.HasSuffix(fqdn, name) {
|
if strings.HasSuffix(fqdn, name) {
|
||||||
if len(zone.Name) > len(hostedZone.Name) {
|
if len(zone.Name) > len(hostedZone.Name) {
|
||||||
hostedZone = zone
|
hostedZone = zone
|
||||||
|
@ -116,7 +119,7 @@ func (c *DNSProviderCloudFlare) getHostedZoneID(fqdn string) (string, error) {
|
||||||
return hostedZone.ID, nil
|
return hostedZone.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSProviderCloudFlare) findTxtRecord(fqdn string) (*cloudFlareRecord, error) {
|
func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) {
|
||||||
zoneID, err := c.getHostedZoneID(fqdn)
|
zoneID, err := c.getHostedZoneID(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -134,7 +137,7 @@ func (c *DNSProviderCloudFlare) findTxtRecord(fqdn string) (*cloudFlareRecord, e
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rec := range records {
|
for _, rec := range records {
|
||||||
if rec.Name == unFqdn(fqdn) && rec.Type == "TXT" {
|
if rec.Name == acme.UnFqdn(fqdn) && rec.Type == "TXT" {
|
||||||
return &rec, nil
|
return &rec, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,7 +145,7 @@ func (c *DNSProviderCloudFlare) findTxtRecord(fqdn string) (*cloudFlareRecord, e
|
||||||
return nil, fmt.Errorf("No existing record found for %s", fqdn)
|
return nil, fmt.Errorf("No existing record found for %s", fqdn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSProviderCloudFlare) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
|
func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
|
||||||
// APIError contains error details for failed requests
|
// APIError contains error details for failed requests
|
||||||
type APIError struct {
|
type APIError struct {
|
||||||
Code int `json:"code,omitempty"`
|
Code int `json:"code,omitempty"`
|
||||||
|
@ -163,7 +166,7 @@ func (c *DNSProviderCloudFlare) makeRequest(method, uri string, body io.Reader)
|
||||||
|
|
||||||
req.Header.Set("X-Auth-Email", c.authEmail)
|
req.Header.Set("X-Auth-Email", c.authEmail)
|
||||||
req.Header.Set("X-Auth-Key", c.authKey)
|
req.Header.Set("X-Auth-Key", c.authKey)
|
||||||
req.Header.Set("User-Agent", userAgent())
|
//req.Header.Set("User-Agent", userAgent())
|
||||||
|
|
||||||
client := http.Client{Timeout: 30 * time.Second}
|
client := http.Client{Timeout: 30 * time.Second}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
|
@ -1,4 +1,4 @@
|
||||||
package acme
|
package cloudflare
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
@ -29,26 +29,26 @@ func restoreCloudFlareEnv() {
|
||||||
os.Setenv("CLOUDFLARE_API_KEY", cflareAPIKey)
|
os.Setenv("CLOUDFLARE_API_KEY", cflareAPIKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDNSProviderCloudFlareValid(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 := NewDNSProviderCloudFlare("123", "123")
|
_, err := NewDNSProvider("123", "123")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
restoreCloudFlareEnv()
|
restoreCloudFlareEnv()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDNSProviderCloudFlareValidEnv(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 := NewDNSProviderCloudFlare("", "")
|
_, err := NewDNSProvider("", "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
restoreCloudFlareEnv()
|
restoreCloudFlareEnv()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDNSProviderCloudFlareMissingCredErr(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 := NewDNSProviderCloudFlare("", "")
|
_, 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 := NewDNSProviderCloudFlare(cflareEmail, cflareAPIKey)
|
provider, err := NewDNSProvider(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 := NewDNSProviderCloudFlare(cflareEmail, cflareAPIKey)
|
provider, err := NewDNSProvider(cflareEmail, cflareAPIKey)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = provider.CleanUp(cflareDomain, "", "123d==")
|
err = provider.CleanUp(cflareDomain, "", "123d==")
|
|
@ -1,4 +1,5 @@
|
||||||
package acme
|
// Package digitalocean implements a DNS provider for solving the DNS-01 challenge using digitalocean DNS.
|
||||||
|
package digitalocean
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -6,28 +7,30 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSProviderDigitalOcean is an implementation of the DNSProvider interface
|
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||||
// that uses DigitalOcean's REST API to manage TXT records for a domain.
|
// that uses DigitalOcean's REST API to manage TXT records for a domain.
|
||||||
type DNSProviderDigitalOcean struct {
|
type DNSProvider struct {
|
||||||
apiAuthToken string
|
apiAuthToken string
|
||||||
recordIDs map[string]int
|
recordIDs map[string]int
|
||||||
recordIDsMu sync.Mutex
|
recordIDsMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDNSProviderDigitalOcean returns a new DNSProviderDigitalOcean instance.
|
// NewDNSProvider returns a new DNSProvider instance.
|
||||||
// apiAuthToken is the personal access token created in the DigitalOcean account
|
// apiAuthToken is the personal access token created in the DigitalOcean account
|
||||||
// control panel, and it will be sent in bearer authorization headers.
|
// control panel, and it will be sent in bearer authorization headers.
|
||||||
func NewDNSProviderDigitalOcean(apiAuthToken string) (*DNSProviderDigitalOcean, error) {
|
func NewDNSProvider(apiAuthToken string) (*DNSProvider, error) {
|
||||||
return &DNSProviderDigitalOcean{
|
return &DNSProvider{
|
||||||
apiAuthToken: apiAuthToken,
|
apiAuthToken: apiAuthToken,
|
||||||
recordIDs: make(map[string]int),
|
recordIDs: make(map[string]int),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present creates a TXT record using the specified parameters
|
// Present creates a TXT record using the specified parameters
|
||||||
func (d *DNSProviderDigitalOcean) Present(domain, token, keyAuth string) error {
|
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
// txtRecordRequest represents the request body to DO's API to make a TXT record
|
// txtRecordRequest represents the request body to DO's API to make a TXT record
|
||||||
type txtRecordRequest struct {
|
type txtRecordRequest struct {
|
||||||
RecordType string `json:"type"`
|
RecordType string `json:"type"`
|
||||||
|
@ -45,7 +48,7 @@ func (d *DNSProviderDigitalOcean) Present(domain, token, keyAuth string) error {
|
||||||
} `json:"domain_record"`
|
} `json:"domain_record"`
|
||||||
}
|
}
|
||||||
|
|
||||||
fqdn, value, _ := DNS01Record(domain, keyAuth)
|
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("%s/v2/domains/%s/records", digitalOceanBaseURL, domain)
|
reqURL := fmt.Sprintf("%s/v2/domains/%s/records", digitalOceanBaseURL, domain)
|
||||||
reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value}
|
reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value}
|
||||||
|
@ -87,8 +90,8 @@ func (d *DNSProviderDigitalOcean) Present(domain, token, keyAuth string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp removes the TXT record matching the specified parameters
|
// CleanUp removes the TXT record matching the specified parameters
|
||||||
func (d *DNSProviderDigitalOcean) CleanUp(domain, token, keyAuth string) error {
|
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
fqdn, _, _ := DNS01Record(domain, keyAuth)
|
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||||
|
|
||||||
// get the record's unique ID from when we created it
|
// get the record's unique ID from when we created it
|
||||||
d.recordIDsMu.Lock()
|
d.recordIDsMu.Lock()
|
|
@ -1,4 +1,4 @@
|
||||||
package acme
|
package digitalocean
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -53,7 +53,7 @@ func TestDigitalOceanPresent(t *testing.T) {
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
digitalOceanBaseURL = mock.URL
|
digitalOceanBaseURL = mock.URL
|
||||||
|
|
||||||
doprov, err := NewDNSProviderDigitalOcean(fakeDigitalOceanAuth)
|
doprov, err := NewDNSProvider(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 := NewDNSProviderDigitalOcean(fakeDigitalOceanAuth)
|
doprov, err := NewDNSProvider(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")
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
package acme
|
// Package dnsimple implements a DNS provider for solving the DNS-01 challenge using dnsimple DNS.
|
||||||
|
package dnsimple
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -6,17 +7,18 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/weppos/dnsimple-go/dnsimple"
|
"github.com/weppos/dnsimple-go/dnsimple"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSProviderDNSimple is an implementation of the DNSProvider interface.
|
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||||
type DNSProviderDNSimple struct {
|
type DNSProvider struct {
|
||||||
client *dnsimple.Client
|
client *dnsimple.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDNSProviderDNSimple returns a DNSProviderDNSimple instance with a configured 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
|
// Authentication is either done using the passed credentials or - when empty - using the environment
|
||||||
// variables DNSIMPLE_EMAIL and DNSIMPLE_API_KEY.
|
// variables DNSIMPLE_EMAIL and DNSIMPLE_API_KEY.
|
||||||
func NewDNSProviderDNSimple(dnsimpleEmail, dnsimpleAPIKey string) (*DNSProviderDNSimple, error) {
|
func NewDNSProvider(dnsimpleEmail, dnsimpleAPIKey string) (*DNSProvider, error) {
|
||||||
if dnsimpleEmail == "" || dnsimpleAPIKey == "" {
|
if dnsimpleEmail == "" || dnsimpleAPIKey == "" {
|
||||||
dnsimpleEmail, dnsimpleAPIKey = dnsimpleEnvAuth()
|
dnsimpleEmail, dnsimpleAPIKey = dnsimpleEnvAuth()
|
||||||
if dnsimpleEmail == "" || dnsimpleAPIKey == "" {
|
if dnsimpleEmail == "" || dnsimpleAPIKey == "" {
|
||||||
|
@ -24,7 +26,7 @@ func NewDNSProviderDNSimple(dnsimpleEmail, dnsimpleAPIKey string) (*DNSProviderD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &DNSProviderDNSimple{
|
c := &DNSProvider{
|
||||||
client: dnsimple.NewClient(dnsimpleAPIKey, dnsimpleEmail),
|
client: dnsimple.NewClient(dnsimpleAPIKey, dnsimpleEmail),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +34,8 @@ func NewDNSProviderDNSimple(dnsimpleEmail, dnsimpleAPIKey string) (*DNSProviderD
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present creates a TXT record to fulfil the dns-01 challenge.
|
// Present creates a TXT record to fulfil the dns-01 challenge.
|
||||||
func (c *DNSProviderDNSimple) Present(domain, token, keyAuth string) error {
|
func (c *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
fqdn, value, ttl := DNS01Record(domain, keyAuth)
|
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||||
|
|
||||||
zoneID, zoneName, err := c.getHostedZone(domain)
|
zoneID, zoneName, err := c.getHostedZone(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -50,8 +52,8 @@ func (c *DNSProviderDNSimple) Present(domain, token, keyAuth string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp removes the TXT record matching the specified parameters.
|
// CleanUp removes the TXT record matching the specified parameters.
|
||||||
func (c *DNSProviderDNSimple) CleanUp(domain, token, keyAuth string) error {
|
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
fqdn, _, _ := DNS01Record(domain, keyAuth)
|
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||||
|
|
||||||
records, err := c.findTxtRecords(domain, fqdn)
|
records, err := c.findTxtRecords(domain, fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -67,7 +69,7 @@ func (c *DNSProviderDNSimple) CleanUp(domain, token, keyAuth string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSProviderDNSimple) getHostedZone(domain string) (string, string, error) {
|
func (c *DNSProvider) getHostedZone(domain string) (string, string, error) {
|
||||||
domains, _, err := c.client.Domains.List()
|
domains, _, err := c.client.Domains.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("DNSimple API call failed: %v", err)
|
return "", "", fmt.Errorf("DNSimple API call failed: %v", err)
|
||||||
|
@ -88,7 +90,7 @@ func (c *DNSProviderDNSimple) getHostedZone(domain string) (string, string, erro
|
||||||
return fmt.Sprintf("%v", hostedDomain.Id), hostedDomain.Name, nil
|
return fmt.Sprintf("%v", hostedDomain.Id), hostedDomain.Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSProviderDNSimple) findTxtRecords(domain, fqdn string) ([]dnsimple.Record, error) {
|
func (c *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnsimple.Record, error) {
|
||||||
zoneID, zoneName, err := c.getHostedZone(domain)
|
zoneID, zoneName, err := c.getHostedZone(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -110,7 +112,7 @@ func (c *DNSProviderDNSimple) findTxtRecords(domain, fqdn string) ([]dnsimple.Re
|
||||||
return records, nil
|
return records, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSProviderDNSimple) newTxtRecord(zone, fqdn, value string, ttl int) *dnsimple.Record {
|
func (c *DNSProvider) newTxtRecord(zone, fqdn, value string, ttl int) *dnsimple.Record {
|
||||||
name := c.extractRecordName(fqdn, zone)
|
name := c.extractRecordName(fqdn, zone)
|
||||||
|
|
||||||
return &dnsimple.Record{
|
return &dnsimple.Record{
|
||||||
|
@ -121,8 +123,8 @@ func (c *DNSProviderDNSimple) newTxtRecord(zone, fqdn, value string, ttl int) *d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSProviderDNSimple) extractRecordName(fqdn, domain string) string {
|
func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||||
name := unFqdn(fqdn)
|
name := acme.UnFqdn(fqdn)
|
||||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||||
return name[:idx]
|
return name[:idx]
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package acme
|
package dnsimple
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
@ -29,25 +29,25 @@ func restoreDNSimpleEnv() {
|
||||||
os.Setenv("DNSIMPLE_API_KEY", dnsimpleAPIKey)
|
os.Setenv("DNSIMPLE_API_KEY", dnsimpleAPIKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDNSProviderDNSimpleValid(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 := NewDNSProviderDNSimple("example@example.com", "123")
|
_, err := NewDNSProvider("example@example.com", "123")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
restoreDNSimpleEnv()
|
restoreDNSimpleEnv()
|
||||||
}
|
}
|
||||||
func TestNewDNSProviderDNSimpleValidEnv(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 := NewDNSProviderDNSimple("", "")
|
_, err := NewDNSProvider("", "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
restoreDNSimpleEnv()
|
restoreDNSimpleEnv()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDNSProviderDNSimpleMissingCredErr(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 := NewDNSProviderDNSimple("", "")
|
_, 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 := NewDNSProviderDNSimple(dnsimpleEmail, dnsimpleAPIKey)
|
provider, err := NewDNSProvider(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 := NewDNSProviderDNSimple(dnsimpleEmail, dnsimpleAPIKey)
|
provider, err := NewDNSProvider(dnsimpleEmail, dnsimpleAPIKey)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = provider.CleanUp(dnsimpleDomain, "", "123d==")
|
err = provider.CleanUp(dnsimpleDomain, "", "123d==")
|
|
@ -1,4 +1,5 @@
|
||||||
package acme
|
// Package rfc2136 implements a DNS provider for solving the DNS-01 challenge using the rfc2136 dynamic update.
|
||||||
|
package rfc2136
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -7,21 +8,22 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSProviderRFC2136 is an implementation of the ChallengeProvider interface that
|
// DNSProvider is an implementation of the acme.ChallengeProvider interface that
|
||||||
// uses dynamic DNS updates (RFC 2136) to create TXT records on a nameserver.
|
// uses dynamic DNS updates (RFC 2136) to create TXT records on a nameserver.
|
||||||
type DNSProviderRFC2136 struct {
|
type DNSProvider struct {
|
||||||
nameserver string
|
nameserver string
|
||||||
tsigAlgorithm string
|
tsigAlgorithm string
|
||||||
tsigKey string
|
tsigKey string
|
||||||
tsigSecret string
|
tsigSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDNSProviderRFC2136 returns a new DNSProviderRFC2136 instance.
|
// NewDNSProvider returns a new DNSProvider instance.
|
||||||
// To disable TSIG authentication 'tsigAlgorithm, 'tsigKey' and 'tsigSecret' must be set to the empty string.
|
// 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".
|
// 'nameserver' must be a network address in the the form "host" or "host:port".
|
||||||
func NewDNSProviderRFC2136(nameserver, tsigAlgorithm, tsigKey, tsigSecret string) (*DNSProviderRFC2136, error) {
|
func NewDNSProvider(nameserver, tsigAlgorithm, tsigKey, tsigSecret string) (*DNSProvider, error) {
|
||||||
// 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") {
|
||||||
|
@ -30,7 +32,7 @@ func NewDNSProviderRFC2136(nameserver, tsigAlgorithm, tsigKey, tsigSecret string
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d := &DNSProviderRFC2136{
|
d := &DNSProvider{
|
||||||
nameserver: nameserver,
|
nameserver: nameserver,
|
||||||
}
|
}
|
||||||
if tsigAlgorithm == "" {
|
if tsigAlgorithm == "" {
|
||||||
|
@ -46,20 +48,20 @@ func NewDNSProviderRFC2136(nameserver, tsigAlgorithm, tsigKey, tsigSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present creates a TXT record using the specified parameters
|
// Present creates a TXT record using the specified parameters
|
||||||
func (r *DNSProviderRFC2136) Present(domain, token, keyAuth string) error {
|
func (r *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
fqdn, value, ttl := DNS01Record(domain, keyAuth)
|
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||||
return r.changeRecord("INSERT", fqdn, value, ttl)
|
return r.changeRecord("INSERT", fqdn, value, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp removes the TXT record matching the specified parameters
|
// CleanUp removes the TXT record matching the specified parameters
|
||||||
func (r *DNSProviderRFC2136) CleanUp(domain, token, keyAuth string) error {
|
func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
fqdn, value, ttl := DNS01Record(domain, keyAuth)
|
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||||
return r.changeRecord("REMOVE", fqdn, value, ttl)
|
return r.changeRecord("REMOVE", fqdn, value, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DNSProviderRFC2136) changeRecord(action, fqdn, value string, ttl int) error {
|
func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
|
||||||
// Find the zone for the given fqdn
|
// Find the zone for the given fqdn
|
||||||
zone, err := findZoneByFqdn(fqdn, r.nameserver)
|
zone, err := acme.FindZoneByFqdn(fqdn, r.nameserver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package acme
|
package rfc2136
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -26,7 +27,7 @@ var (
|
||||||
var reqChan = make(chan *dns.Msg, 10)
|
var reqChan = make(chan *dns.Msg, 10)
|
||||||
|
|
||||||
func TestRFC2136CanaryLocalTestServer(t *testing.T) {
|
func TestRFC2136CanaryLocalTestServer(t *testing.T) {
|
||||||
clearFqdnCache()
|
acme.ClearFqdnCache()
|
||||||
dns.HandleFunc("example.com.", serverHandlerHello)
|
dns.HandleFunc("example.com.", serverHandlerHello)
|
||||||
defer dns.HandleRemove("example.com.")
|
defer dns.HandleRemove("example.com.")
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ func TestRFC2136CanaryLocalTestServer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRFC2136ServerSuccess(t *testing.T) {
|
func TestRFC2136ServerSuccess(t *testing.T) {
|
||||||
clearFqdnCache()
|
acme.ClearFqdnCache()
|
||||||
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnSuccess)
|
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnSuccess)
|
||||||
defer dns.HandleRemove(rfc2136TestZone)
|
defer dns.HandleRemove(rfc2136TestZone)
|
||||||
|
|
||||||
|
@ -60,9 +61,9 @@ func TestRFC2136ServerSuccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer server.Shutdown()
|
defer server.Shutdown()
|
||||||
|
|
||||||
provider, err := NewDNSProviderRFC2136(addrstr, "", "", "")
|
provider, err := NewDNSProvider(addrstr, "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected NewDNSProviderRFC2136() to return no error but the error was -> %v", err)
|
t.Fatalf("Expected NewDNSProvider() 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)
|
||||||
|
@ -70,7 +71,7 @@ func TestRFC2136ServerSuccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRFC2136ServerError(t *testing.T) {
|
func TestRFC2136ServerError(t *testing.T) {
|
||||||
clearFqdnCache()
|
acme.ClearFqdnCache()
|
||||||
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnErr)
|
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnErr)
|
||||||
defer dns.HandleRemove(rfc2136TestZone)
|
defer dns.HandleRemove(rfc2136TestZone)
|
||||||
|
|
||||||
|
@ -80,9 +81,9 @@ func TestRFC2136ServerError(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer server.Shutdown()
|
defer server.Shutdown()
|
||||||
|
|
||||||
provider, err := NewDNSProviderRFC2136(addrstr, "", "", "")
|
provider, err := NewDNSProvider(addrstr, "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected NewDNSProviderRFC2136() to return no error but the error was -> %v", err)
|
t.Fatalf("Expected NewDNSProvider() 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.")
|
||||||
|
@ -92,7 +93,7 @@ func TestRFC2136ServerError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRFC2136TsigClient(t *testing.T) {
|
func TestRFC2136TsigClient(t *testing.T) {
|
||||||
clearFqdnCache()
|
acme.ClearFqdnCache()
|
||||||
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnSuccess)
|
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnSuccess)
|
||||||
defer dns.HandleRemove(rfc2136TestZone)
|
defer dns.HandleRemove(rfc2136TestZone)
|
||||||
|
|
||||||
|
@ -102,9 +103,9 @@ func TestRFC2136TsigClient(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer server.Shutdown()
|
defer server.Shutdown()
|
||||||
|
|
||||||
provider, err := NewDNSProviderRFC2136(addrstr, "", rfc2136TestTsigKey, rfc2136TestTsigSecret)
|
provider, err := NewDNSProvider(addrstr, "", rfc2136TestTsigKey, rfc2136TestTsigSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected NewDNSProviderRFC2136() to return no error but the error was -> %v", err)
|
t.Fatalf("Expected NewDNSProvider() 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)
|
||||||
|
@ -112,7 +113,7 @@ func TestRFC2136TsigClient(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRFC2136ValidUpdatePacket(t *testing.T) {
|
func TestRFC2136ValidUpdatePacket(t *testing.T) {
|
||||||
clearFqdnCache()
|
acme.ClearFqdnCache()
|
||||||
dns.HandleFunc(rfc2136TestZone, serverHandlerPassBackRequest)
|
dns.HandleFunc(rfc2136TestZone, serverHandlerPassBackRequest)
|
||||||
defer dns.HandleRemove(rfc2136TestZone)
|
defer dns.HandleRemove(rfc2136TestZone)
|
||||||
|
|
||||||
|
@ -134,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 := NewDNSProviderRFC2136(addrstr, "", "", "")
|
provider, err := NewDNSProvider(addrstr, "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected NewDNSProviderRFC2136() to return no error but the error was -> %v", err)
|
t.Fatalf("Expected NewDNSProvider() 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 {
|
|
@ -1,4 +1,5 @@
|
||||||
package acme
|
// Package route53 implements a DNS provider for solving the DNS-01 challenge using route53 DNS.
|
||||||
|
package route53
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -7,20 +8,21 @@ import (
|
||||||
|
|
||||||
"github.com/mitchellh/goamz/aws"
|
"github.com/mitchellh/goamz/aws"
|
||||||
"github.com/mitchellh/goamz/route53"
|
"github.com/mitchellh/goamz/route53"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSProviderRoute53 is an implementation of the DNSProvider interface
|
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||||
type DNSProviderRoute53 struct {
|
type DNSProvider struct {
|
||||||
client *route53.Route53
|
client *route53.Route53
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDNSProviderRoute53 returns a DNSProviderRoute53 instance with a configured route53 client.
|
// NewDNSProvider returns a DNSProvider instance with a configured route53 client.
|
||||||
// Authentication is either done using the passed credentials or - when empty - falling back to
|
// 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
|
// the customary AWS credential mechanisms, including the file referenced by $AWS_CREDENTIAL_FILE
|
||||||
// (defaulting to $HOME/.aws/credentials) optionally scoped to $AWS_PROFILE, credentials
|
// (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 ],
|
// 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.
|
// and finally credentials available via the EC2 instance metadata service.
|
||||||
func NewDNSProviderRoute53(awsAccessKey, awsSecretKey, awsRegionName string) (*DNSProviderRoute53, error) {
|
func NewDNSProvider(awsAccessKey, awsSecretKey, awsRegionName string) (*DNSProvider, error) {
|
||||||
region, ok := aws.Regions[awsRegionName]
|
region, ok := aws.Regions[awsRegionName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Invalid AWS region name %s", awsRegionName)
|
return nil, fmt.Errorf("Invalid AWS region name %s", awsRegionName)
|
||||||
|
@ -38,24 +40,24 @@ func NewDNSProviderRoute53(awsAccessKey, awsSecretKey, awsRegionName string) (*D
|
||||||
}
|
}
|
||||||
|
|
||||||
client := route53.New(auth, region)
|
client := route53.New(auth, region)
|
||||||
return &DNSProviderRoute53{client: client}, nil
|
return &DNSProvider{client: client}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present creates a TXT record using the specified parameters
|
// Present creates a TXT record using the specified parameters
|
||||||
func (r *DNSProviderRoute53) Present(domain, token, keyAuth string) error {
|
func (r *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
fqdn, value, ttl := DNS01Record(domain, keyAuth)
|
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||||
value = `"` + value + `"`
|
value = `"` + value + `"`
|
||||||
return r.changeRecord("UPSERT", fqdn, value, ttl)
|
return r.changeRecord("UPSERT", fqdn, value, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp removes the TXT record matching the specified parameters
|
// CleanUp removes the TXT record matching the specified parameters
|
||||||
func (r *DNSProviderRoute53) CleanUp(domain, token, keyAuth string) error {
|
func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
fqdn, value, ttl := DNS01Record(domain, keyAuth)
|
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||||
value = `"` + value + `"`
|
value = `"` + value + `"`
|
||||||
return r.changeRecord("DELETE", fqdn, value, ttl)
|
return r.changeRecord("DELETE", fqdn, value, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DNSProviderRoute53) changeRecord(action, fqdn, value string, ttl int) error {
|
func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
|
||||||
hostedZoneID, err := r.getHostedZoneID(fqdn)
|
hostedZoneID, err := r.getHostedZoneID(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -69,7 +71,7 @@ func (r *DNSProviderRoute53) changeRecord(action, fqdn, value string, ttl int) e
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitFor(90, 5, func() (bool, error) {
|
return acme.WaitFor(90*time.Second, 5*time.Second, func() (bool, error) {
|
||||||
status, err := r.client.GetChange(resp.ChangeInfo.ID)
|
status, err := r.client.GetChange(resp.ChangeInfo.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -81,7 +83,7 @@ func (r *DNSProviderRoute53) changeRecord(action, fqdn, value string, ttl int) e
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DNSProviderRoute53) getHostedZoneID(fqdn string) (string, error) {
|
func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
|
||||||
zones := []route53.HostedZone{}
|
zones := []route53.HostedZone{}
|
||||||
zoneResp, err := r.client.ListHostedZones("", 0)
|
zoneResp, err := r.client.ListHostedZones("", 0)
|
||||||
if err != nil {
|
if err != nil {
|
|
@ -1,4 +1,4 @@
|
||||||
package acme
|
package route53
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -91,29 +91,29 @@ func makeRoute53TestServer() *testutil.HTTPServer {
|
||||||
return testServer
|
return testServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRoute53Provider(server *testutil.HTTPServer) *DNSProviderRoute53 {
|
func makeRoute53Provider(server *testutil.HTTPServer) *DNSProvider {
|
||||||
auth := aws.Auth{AccessKey: "abc", SecretKey: "123", Token: ""}
|
auth := aws.Auth{AccessKey: "abc", SecretKey: "123", Token: ""}
|
||||||
client := route53.NewWithClient(auth, aws.Region{Route53Endpoint: server.URL}, testutil.DefaultClient)
|
client := route53.NewWithClient(auth, aws.Region{Route53Endpoint: server.URL}, testutil.DefaultClient)
|
||||||
return &DNSProviderRoute53{client: client}
|
return &DNSProvider{client: client}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDNSProviderRoute53Valid(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 := NewDNSProviderRoute53("123", "123", "us-east-1")
|
_, err := NewDNSProvider("123", "123", "us-east-1")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
restoreRoute53Env()
|
restoreRoute53Env()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDNSProviderRoute53ValidEnv(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 := NewDNSProviderRoute53("", "", "us-east-1")
|
_, err := NewDNSProvider("", "", "us-east-1")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
restoreRoute53Env()
|
restoreRoute53Env()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDNSProviderRoute53MissingAuthErr(t *testing.T) {
|
func TestNewDNSProviderMissingAuthErr(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", "")
|
||||||
os.Setenv("AWS_CREDENTIAL_FILE", "") // in case test machine has this variable set
|
os.Setenv("AWS_CREDENTIAL_FILE", "") // in case test machine has this variable set
|
||||||
|
@ -124,7 +124,7 @@ func TestNewDNSProviderRoute53MissingAuthErr(t *testing.T) {
|
||||||
awsClient := aws.RetryingClient
|
awsClient := aws.RetryingClient
|
||||||
aws.RetryingClient = &http.Client{Timeout: time.Millisecond}
|
aws.RetryingClient = &http.Client{Timeout: time.Millisecond}
|
||||||
|
|
||||||
_, err := NewDNSProviderRoute53("", "", "us-east-1")
|
_, err := NewDNSProvider("", "", "us-east-1")
|
||||||
assert.EqualError(t, err, "No valid AWS authentication found")
|
assert.EqualError(t, err, "No valid AWS authentication found")
|
||||||
restoreRoute53Env()
|
restoreRoute53Env()
|
||||||
|
|
||||||
|
@ -132,8 +132,8 @@ func TestNewDNSProviderRoute53MissingAuthErr(t *testing.T) {
|
||||||
aws.RetryingClient = awsClient
|
aws.RetryingClient = awsClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDNSProviderRoute53InvalidRegionErr(t *testing.T) {
|
func TestNewDNSProviderInvalidRegionErr(t *testing.T) {
|
||||||
_, err := NewDNSProviderRoute53("123", "123", "us-east-3")
|
_, err := NewDNSProvider("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")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue