Support custom DNS resolvers for Let's Encrypt.

This commit is contained in:
Ludovic Fernandez 2018-10-25 17:38:04 +02:00 committed by Traefiker Bot
parent ac11323fdd
commit 74dc5b1c58
5 changed files with 113 additions and 4 deletions

View file

@ -451,6 +451,9 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
return nil, err return nil, err
} }
acmeprovider.SetRecursiveNameServers(a.DNSChallenge.Resolvers)
acmeprovider.SetPropagationCheck(a.DNSChallenge.DisablePropagationCheck)
var provider acme.ChallengeProvider var provider acme.ChallengeProvider
provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider) provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider)
if err != nil { if err != nil {

View file

@ -71,6 +71,7 @@ Complete documentation is available at https://traefik.io`,
f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{}) f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{})
f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{}) f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{})
f.AddParser(reflect.TypeOf([]types.Domain{}), &types.Domains{}) 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.Buckets{}), &types.Buckets{})
f.AddParser(reflect.TypeOf(types.StatusCodes{}), &types.StatusCodes{}) f.AddParser(reflect.TypeOf(types.StatusCodes{}), &types.StatusCodes{})
f.AddParser(reflect.TypeOf(types.FieldNames{}), &types.FieldNames{}) f.AddParser(reflect.TypeOf(types.FieldNames{}), &types.FieldNames{})

View file

@ -142,6 +142,23 @@ entryPoint = "https"
# #
# delayBeforeCheck = 0 # 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. # Domains list.
# Only domains defined here can generate wildcard certificates. # Only domains defined here can generate wildcard certificates.
# The certificates for these domains are negotiated at traefik startup only. # The certificates for these domains are negotiated at traefik startup only.
@ -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 | | [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 | | [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
#### `resolvers`
Use custom DNS servers to resolve the FQDN authority.
### `domains` ### `domains`
You can provide SANs (alternative domains) to each main domain. You can provide SANs (alternative domains) to each main domain.

View file

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
fmtlog "log" fmtlog "log"
"net"
"net/url" "net/url"
"reflect" "reflect"
"strings" "strings"
@ -74,10 +75,12 @@ type Certificate struct {
// DNSChallenge contains DNS challenge Configuration // DNSChallenge contains DNS challenge Configuration
type DNSChallenge struct { type DNSChallenge struct {
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."` 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."` DelayBeforeCheck flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
preCheckTimeout time.Duration Resolvers types.DNSResolvers `description:"Use following DNS servers to resolve the FQDN authority."`
preCheckInterval time.Duration 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 // HTTPChallenge contains HTTP challenge Configuration
@ -252,6 +255,9 @@ func (p *Provider) getClient() (*acme.Client, error) {
if p.DNSChallenge != nil && len(p.DNSChallenge.Provider) > 0 { if p.DNSChallenge != nil && len(p.DNSChallenge.Provider) > 0 {
log.Debugf("Using DNS Challenge provider: %s", p.DNSChallenge.Provider) log.Debugf("Using DNS Challenge provider: %s", p.DNSChallenge.Provider)
SetRecursiveNameServers(p.DNSChallenge.Resolvers)
SetPropagationCheck(p.DNSChallenge.DisablePropagationCheck)
err = dnsOverrideDelay(p.DNSChallenge.DelayBeforeCheck) err = dnsOverrideDelay(p.DNSChallenge.DelayBeforeCheck)
if err != nil { if err != nil {
return nil, err return nil, err
@ -784,3 +790,37 @@ func isDomainAlreadyChecked(domainToCheck string, existentDomains []string) bool
} }
return false 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
types/dns_resolvers.go Normal file
View file

@ -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"
}