Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom DNS resolvers for Let's Encrypt. #4101

Merged
merged 3 commits into from
Oct 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions acme/acme.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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"
}