Skip to content

Commit

Permalink
Support custom DNS resolvers for Let's Encrypt.
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez authored and traefiker committed Oct 25, 2018
1 parent ac11323 commit 74dc5b1
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 4 deletions.
3 changes: 3 additions & 0 deletions acme/acme.go
Expand Up @@ -451,6 +451,9 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
return nil, err
}

acmeprovider.SetRecursiveNameServers(a.DNSChallenge.Resolvers)
acmeprovider.SetPropagationCheck(a.DNSChallenge.DisablePropagationCheck)

var provider acme.ChallengeProvider
provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions cmd/traefik/traefik.go
Expand Up @@ -71,6 +71,7 @@ Complete documentation is available at https://traefik.io`,
f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{})
f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{})
f.AddParser(reflect.TypeOf([]types.Domain{}), &types.Domains{})
f.AddParser(reflect.TypeOf(types.DNSResolvers{}), &types.DNSResolvers{})
f.AddParser(reflect.TypeOf(types.Buckets{}), &types.Buckets{})
f.AddParser(reflect.TypeOf(types.StatusCodes{}), &types.StatusCodes{})
f.AddParser(reflect.TypeOf(types.FieldNames{}), &types.FieldNames{})
Expand Down
21 changes: 21 additions & 0 deletions docs/configuration/acme.md
Expand Up @@ -142,6 +142,23 @@ entryPoint = "https"
#
# delayBeforeCheck = 0

# Use following DNS servers to resolve the FQDN authority.
#
# Optional
# Default: empty
#
# resolvers = ["1.1.1.1:53", "8.8.8.8:53"]

# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
#
# NOT RECOMMENDED:
# Increase the risk of reaching Let's Encrypt's rate limits.
#
# Optional
# Default: false
#
# disablePropagationCheck = true

# Domains list.
# Only domains defined here can generate wildcard certificates.
# The certificates for these domains are negotiated at traefik startup only.
Expand Down Expand Up @@ -302,6 +319,10 @@ Here is a list of supported `provider`s, that can automate the DNS verification,
| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | Not tested yet |
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |

#### `resolvers`

Use custom DNS servers to resolve the FQDN authority.

### `domains`

You can provide SANs (alternative domains) to each main domain.
Expand Down
48 changes: 44 additions & 4 deletions provider/acme/provider.go
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
fmtlog "log"
"net"
"net/url"
"reflect"
"strings"
Expand Down Expand Up @@ -74,10 +75,12 @@ type Certificate struct {

// DNSChallenge contains DNS challenge Configuration
type DNSChallenge struct {
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
DelayBeforeCheck flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
preCheckTimeout time.Duration
preCheckInterval time.Duration
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
DelayBeforeCheck flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
Resolvers types.DNSResolvers `description:"Use following DNS servers to resolve the FQDN authority."`
DisablePropagationCheck bool `description:"Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]"`
preCheckTimeout time.Duration
preCheckInterval time.Duration
}

// HTTPChallenge contains HTTP challenge Configuration
Expand Down Expand Up @@ -252,6 +255,9 @@ func (p *Provider) getClient() (*acme.Client, error) {
if p.DNSChallenge != nil && len(p.DNSChallenge.Provider) > 0 {
log.Debugf("Using DNS Challenge provider: %s", p.DNSChallenge.Provider)

SetRecursiveNameServers(p.DNSChallenge.Resolvers)
SetPropagationCheck(p.DNSChallenge.DisablePropagationCheck)

err = dnsOverrideDelay(p.DNSChallenge.DelayBeforeCheck)
if err != nil {
return nil, err
Expand Down Expand Up @@ -784,3 +790,37 @@ func isDomainAlreadyChecked(domainToCheck string, existentDomains []string) bool
}
return false
}

// SetPropagationCheck to disable the Lego PreCheck.
func SetPropagationCheck(disable bool) {
if disable {
acme.PreCheckDNS = func(_, _ string) (bool, error) {
return true, nil
}
}
}

// SetRecursiveNameServers to provide a custom DNS resolver.
func SetRecursiveNameServers(dnsResolvers []string) {
resolvers := normaliseDNSResolvers(dnsResolvers)
if len(resolvers) > 0 {
acme.RecursiveNameservers = resolvers
log.Infof("Validating FQDN authority with DNS using %+v", resolvers)
}
}

// ensure all servers have a port number
func normaliseDNSResolvers(dnsResolvers []string) []string {
var normalisedResolvers []string
for _, server := range dnsResolvers {
srv := strings.TrimSpace(server)
if len(srv) > 0 {
if host, port, err := net.SplitHostPort(srv); err != nil {
normalisedResolvers = append(normalisedResolvers, net.JoinHostPort(srv, "53"))
} else {
normalisedResolvers = append(normalisedResolvers, net.JoinHostPort(host, port))
}
}
}
return normalisedResolvers
}
44 changes: 44 additions & 0 deletions types/dns_resolvers.go
@@ -0,0 +1,44 @@
package types

import (
"fmt"
"strings"
)

// DNSResolvers is a list of DNSes that we will try to resolve the challenged FQDN against
type DNSResolvers []string

// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (r *DNSResolvers) String() string {
return strings.Join(*r, ",")
}

// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it.
func (r *DNSResolvers) Set(value string) error {
entryPoints := strings.Split(value, ",")
if len(entryPoints) == 0 {
return fmt.Errorf("wrong DNSResolvers format: %s", value)
}
for _, entryPoint := range entryPoints {
*r = append(*r, entryPoint)
}
return nil
}

// Get return the DNSResolvers list
func (r *DNSResolvers) Get() interface{} {
return *r
}

// SetValue sets the DNSResolvers list
func (r *DNSResolvers) SetValue(val interface{}) {
*r = val.(DNSResolvers)
}

// Type is type of the struct
func (r *DNSResolvers) Type() string {
return "dnsresolvers"
}

0 comments on commit 74dc5b1

Please sign in to comment.