diff --git a/acme/dns_challenge.go b/acme/dns_challenge.go index 533431ca..49c1a264 100644 --- a/acme/dns_challenge.go +++ b/acme/dns_challenge.go @@ -234,24 +234,6 @@ func findZoneByFqdn(fqdn, nameserver string) (string, error) { 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. -func toFqdn(name string) string { - n := len(name) - if n == 0 || name[n-1] == '.' { - return name - } - return name + "." -} - -// unFqdn converts the fqdn into a name removing the trailing dot. -func unFqdn(name string) string { - n := len(name) - if n != 0 && name[n-1] == '.' { - return name[:n-1] - } - return name -} - // clearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing. func clearFqdnCache() { fqdnToZone = map[string]string{} diff --git a/cli_handlers.go b/cli_handlers.go index 9fc2ca0e..d360a407 100644 --- a/cli_handlers.go +++ b/cli_handlers.go @@ -11,6 +11,11 @@ import ( "github.com/codegangsta/cli" "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 { @@ -67,23 +72,23 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) { var provider acme.ChallengeProvider switch c.GlobalString("dns") { case "cloudflare": - provider, err = acme.NewDNSProviderCloudFlare("", "") + provider, err = cloudflare.NewDNSProviderCloudFlare("", "") case "digitalocean": authToken := os.Getenv("DO_AUTH_TOKEN") - provider, err = acme.NewDNSProviderDigitalOcean(authToken) + provider, err = digitalocean.NewDNSProviderDigitalOcean(authToken) case "dnsimple": - provider, err = acme.NewDNSProviderDNSimple("", "") + provider, err = dnsimple.NewDNSProviderDNSimple("", "") case "route53": awsRegion := os.Getenv("AWS_REGION") - provider, err = acme.NewDNSProviderRoute53("", "", awsRegion) + provider, err = route53.NewDNSProviderRoute53("", "", 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 = acme.NewDNSProviderRFC2136(nameserver, tsigAlgorithm, tsigKey, tsigSecret) + provider, err = rfc2136.NewDNSProviderRFC2136(nameserver, tsigAlgorithm, tsigKey, tsigSecret) case "manual": provider, err = acme.NewDNSProviderManual() } diff --git a/acme/dns_challenge_cloudflare.go b/providers/dns/cloudflare/cloudflare.go similarity index 93% rename from acme/dns_challenge_cloudflare.go rename to providers/dns/cloudflare/cloudflare.go index b5bf6d1f..2b9337c4 100644 --- a/acme/dns_challenge_cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -1,4 +1,4 @@ -package acme +package cloudflare import ( "bytes" @@ -9,6 +9,9 @@ import ( "os" "strings" "time" + + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/providers/dns" ) // CloudFlareAPIURL represents the API endpoint to call. @@ -39,7 +42,7 @@ func NewDNSProviderCloudFlare(cloudflareEmail, cloudflareKey string) (*DNSProvid // Present creates a TXT record to fulfil the dns-01 challenge func (c *DNSProviderCloudFlare) Present(domain, token, keyAuth string) error { - fqdn, value, _ := DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) zoneID, err := c.getHostedZoneID(fqdn) if err != nil { return err @@ -47,7 +50,7 @@ func (c *DNSProviderCloudFlare) Present(domain, token, keyAuth string) error { rec := cloudFlareRecord{ Type: "TXT", - Name: unFqdn(fqdn), + Name: dns.UnFqdn(fqdn), Content: value, TTL: 120, } @@ -67,7 +70,7 @@ func (c *DNSProviderCloudFlare) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters func (c *DNSProviderCloudFlare) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) record, err := c.findTxtRecord(fqdn) if err != nil { @@ -102,7 +105,7 @@ func (c *DNSProviderCloudFlare) getHostedZoneID(fqdn string) (string, error) { var hostedZone HostedZone for _, zone := range zones { - name := toFqdn(zone.Name) + name := dns.ToFqdn(zone.Name) if strings.HasSuffix(fqdn, name) { if len(zone.Name) > len(hostedZone.Name) { hostedZone = zone @@ -134,7 +137,7 @@ func (c *DNSProviderCloudFlare) findTxtRecord(fqdn string) (*cloudFlareRecord, e } for _, rec := range records { - if rec.Name == unFqdn(fqdn) && rec.Type == "TXT" { + if rec.Name == dns.UnFqdn(fqdn) && rec.Type == "TXT" { return &rec, nil } } @@ -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-Key", c.authKey) - req.Header.Set("User-Agent", userAgent()) + //req.Header.Set("User-Agent", userAgent()) client := http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) diff --git a/acme/dns_challenge_cloudflare_test.go b/providers/dns/cloudflare/cloudflare_test.go similarity index 98% rename from acme/dns_challenge_cloudflare_test.go rename to providers/dns/cloudflare/cloudflare_test.go index e628eba1..27b5c357 100644 --- a/acme/dns_challenge_cloudflare_test.go +++ b/providers/dns/cloudflare/cloudflare_test.go @@ -1,4 +1,4 @@ -package acme +package cloudflare import ( "os" diff --git a/acme/dns_challenge_digitalocean.go b/providers/dns/digitalocean/digitalocean.go similarity index 95% rename from acme/dns_challenge_digitalocean.go rename to providers/dns/digitalocean/digitalocean.go index 176439e6..34750265 100644 --- a/acme/dns_challenge_digitalocean.go +++ b/providers/dns/digitalocean/digitalocean.go @@ -1,4 +1,4 @@ -package acme +package digitalocean import ( "bytes" @@ -6,6 +6,8 @@ import ( "fmt" "net/http" "sync" + + "github.com/xenolf/lego/acme" ) // DNSProviderDigitalOcean is an implementation of the DNSProvider interface @@ -45,7 +47,7 @@ func (d *DNSProviderDigitalOcean) Present(domain, token, keyAuth string) error { } `json:"domain_record"` } - fqdn, value, _ := DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) reqURL := fmt.Sprintf("%s/v2/domains/%s/records", digitalOceanBaseURL, domain) reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value} @@ -88,7 +90,7 @@ func (d *DNSProviderDigitalOcean) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters func (d *DNSProviderDigitalOcean) 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 d.recordIDsMu.Lock() diff --git a/acme/dns_challenge_digitalocean_test.go b/providers/dns/digitalocean/digitalocean_test.go similarity index 99% rename from acme/dns_challenge_digitalocean_test.go rename to providers/dns/digitalocean/digitalocean_test.go index cf62090e..52bdedac 100644 --- a/acme/dns_challenge_digitalocean_test.go +++ b/providers/dns/digitalocean/digitalocean_test.go @@ -1,4 +1,4 @@ -package acme +package digitalocean import ( "fmt" diff --git a/acme/dns_challenge_dnsimple.go b/providers/dns/dnsimple/dnsimple.go similarity index 94% rename from acme/dns_challenge_dnsimple.go rename to providers/dns/dnsimple/dnsimple.go index 56b96f97..8b4f95e1 100644 --- a/acme/dns_challenge_dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -1,4 +1,4 @@ -package acme +package dnsimple import ( "fmt" @@ -6,6 +6,8 @@ import ( "strings" "github.com/weppos/dnsimple-go/dnsimple" + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/providers/dns" ) // DNSProviderDNSimple is an implementation of the DNSProvider interface. @@ -33,7 +35,7 @@ func NewDNSProviderDNSimple(dnsimpleEmail, dnsimpleAPIKey string) (*DNSProviderD // Present creates a TXT record to fulfil the dns-01 challenge. func (c *DNSProviderDNSimple) 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) if err != nil { @@ -51,7 +53,7 @@ func (c *DNSProviderDNSimple) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (c *DNSProviderDNSimple) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) records, err := c.findTxtRecords(domain, fqdn) if err != nil { @@ -122,7 +124,7 @@ func (c *DNSProviderDNSimple) newTxtRecord(zone, fqdn, value string, ttl int) *d } func (c *DNSProviderDNSimple) extractRecordName(fqdn, domain string) string { - name := unFqdn(fqdn) + name := dns.UnFqdn(fqdn) if idx := strings.Index(name, "."+domain); idx != -1 { return name[:idx] } diff --git a/acme/dns_challenge_dnsimple_test.go b/providers/dns/dnsimple/dnsimple_test.go similarity index 99% rename from acme/dns_challenge_dnsimple_test.go rename to providers/dns/dnsimple/dnsimple_test.go index 3a85f1f9..c5091949 100644 --- a/acme/dns_challenge_dnsimple_test.go +++ b/providers/dns/dnsimple/dnsimple_test.go @@ -1,4 +1,4 @@ -package acme +package dnsimple import ( "os" diff --git a/acme/dns_challenge_rfc2136.go b/providers/dns/rfc2136/rfc2136.go similarity index 94% rename from acme/dns_challenge_rfc2136.go rename to providers/dns/rfc2136/rfc2136.go index 7c6900de..b2832d50 100644 --- a/acme/dns_challenge_rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -1,4 +1,4 @@ -package acme +package rfc2136 import ( "fmt" @@ -7,6 +7,7 @@ import ( "time" "github.com/miekg/dns" + "github.com/xenolf/lego/acme" ) // DNSProviderRFC2136 is an implementation of the ChallengeProvider interface that @@ -47,13 +48,13 @@ func NewDNSProviderRFC2136(nameserver, tsigAlgorithm, tsigKey, tsigSecret string // Present creates a TXT record using the specified parameters func (r *DNSProviderRFC2136) 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) } // CleanUp removes the TXT record matching the specified parameters func (r *DNSProviderRFC2136) 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) } diff --git a/acme/dns_challenge_rfc2136_test.go b/providers/dns/rfc2136/rfc2136_test.go similarity index 99% rename from acme/dns_challenge_rfc2136_test.go rename to providers/dns/rfc2136/rfc2136_test.go index 1a0ed5cc..2b8fb9cd 100644 --- a/acme/dns_challenge_rfc2136_test.go +++ b/providers/dns/rfc2136/rfc2136_test.go @@ -1,4 +1,4 @@ -package acme +package rfc2136 import ( "bytes" diff --git a/acme/dns_challenge_route53.go b/providers/dns/route53/route53.go similarity index 94% rename from acme/dns_challenge_route53.go rename to providers/dns/route53/route53.go index 43e42dab..fc8cd8cc 100644 --- a/acme/dns_challenge_route53.go +++ b/providers/dns/route53/route53.go @@ -1,4 +1,4 @@ -package acme +package route53 import ( "fmt" @@ -7,6 +7,8 @@ import ( "github.com/mitchellh/goamz/aws" "github.com/mitchellh/goamz/route53" + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/providers/dns" ) // DNSProviderRoute53 is an implementation of the DNSProvider interface @@ -43,14 +45,14 @@ func NewDNSProviderRoute53(awsAccessKey, awsSecretKey, awsRegionName string) (*D // Present creates a TXT record using the specified parameters func (r *DNSProviderRoute53) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) value = `"` + value + `"` return r.changeRecord("UPSERT", fqdn, value, ttl) } // CleanUp removes the TXT record matching the specified parameters func (r *DNSProviderRoute53) CleanUp(domain, token, keyAuth string) error { - fqdn, value, ttl := DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) value = `"` + value + `"` return r.changeRecord("DELETE", fqdn, value, ttl) } @@ -69,7 +71,7 @@ func (r *DNSProviderRoute53) changeRecord(action, fqdn, value string, ttl int) e return err } - return waitFor(90, 5, func() (bool, error) { + return dns.WaitFor(90, 5, func() (bool, error) { status, err := r.client.GetChange(resp.ChangeInfo.ID) if err != nil { return false, err diff --git a/acme/dns_challenge_route53_test.go b/providers/dns/route53/route53_test.go similarity index 99% rename from acme/dns_challenge_route53_test.go rename to providers/dns/route53/route53_test.go index 2c58b263..03f1fa8b 100644 --- a/acme/dns_challenge_route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -1,4 +1,4 @@ -package acme +package route53 import ( "net/http" diff --git a/providers/dns/utils.go b/providers/dns/utils.go new file mode 100644 index 00000000..9df2a8bb --- /dev/null +++ b/providers/dns/utils.go @@ -0,0 +1,47 @@ +package dns + +import ( + "fmt" + "time" +) + +// ToFqdn converts the name into a fqdn appending a trailing dot. +func ToFqdn(name string) string { + n := len(name) + if n == 0 || name[n-1] == '.' { + return name + } + return name + "." +} + +// UnFqdn converts the fqdn into a name removing the trailing dot. +func UnFqdn(name string) string { + n := len(name) + if n != 0 && name[n-1] == '.' { + return name[:n-1] + } + return name +} + +// 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) + } +}