Skip to content

Commit 74dc5b1

Browse files
ldeztraefiker
authored andcommittedOct 25, 2018
Support custom DNS resolvers for Let's Encrypt.
1 parent ac11323 commit 74dc5b1

File tree

5 files changed

+113
-4
lines changed

5 files changed

+113
-4
lines changed
 

‎acme/acme.go

+3
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,9 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
451451
return nil, err
452452
}
453453

454+
acmeprovider.SetRecursiveNameServers(a.DNSChallenge.Resolvers)
455+
acmeprovider.SetPropagationCheck(a.DNSChallenge.DisablePropagationCheck)
456+
454457
var provider acme.ChallengeProvider
455458
provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider)
456459
if err != nil {

‎cmd/traefik/traefik.go

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ Complete documentation is available at https://traefik.io`,
7171
f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{})
7272
f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{})
7373
f.AddParser(reflect.TypeOf([]types.Domain{}), &types.Domains{})
74+
f.AddParser(reflect.TypeOf(types.DNSResolvers{}), &types.DNSResolvers{})
7475
f.AddParser(reflect.TypeOf(types.Buckets{}), &types.Buckets{})
7576
f.AddParser(reflect.TypeOf(types.StatusCodes{}), &types.StatusCodes{})
7677
f.AddParser(reflect.TypeOf(types.FieldNames{}), &types.FieldNames{})

‎docs/configuration/acme.md

+21
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,23 @@ entryPoint = "https"
142142
#
143143
# delayBeforeCheck = 0
144144

145+
# Use following DNS servers to resolve the FQDN authority.
146+
#
147+
# Optional
148+
# Default: empty
149+
#
150+
# resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
151+
152+
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
153+
#
154+
# NOT RECOMMENDED:
155+
# Increase the risk of reaching Let's Encrypt's rate limits.
156+
#
157+
# Optional
158+
# Default: false
159+
#
160+
# disablePropagationCheck = true
161+
145162
# Domains list.
146163
# Only domains defined here can generate wildcard certificates.
147164
# 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,
302319
| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | Not tested yet |
303320
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
304321

322+
#### `resolvers`
323+
324+
Use custom DNS servers to resolve the FQDN authority.
325+
305326
### `domains`
306327

307328
You can provide SANs (alternative domains) to each main domain.

‎provider/acme/provider.go

+44-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io/ioutil"
88
fmtlog "log"
9+
"net"
910
"net/url"
1011
"reflect"
1112
"strings"
@@ -74,10 +75,12 @@ type Certificate struct {
7475

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

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

258+
SetRecursiveNameServers(p.DNSChallenge.Resolvers)
259+
SetPropagationCheck(p.DNSChallenge.DisablePropagationCheck)
260+
255261
err = dnsOverrideDelay(p.DNSChallenge.DelayBeforeCheck)
256262
if err != nil {
257263
return nil, err
@@ -784,3 +790,37 @@ func isDomainAlreadyChecked(domainToCheck string, existentDomains []string) bool
784790
}
785791
return false
786792
}
793+
794+
// SetPropagationCheck to disable the Lego PreCheck.
795+
func SetPropagationCheck(disable bool) {
796+
if disable {
797+
acme.PreCheckDNS = func(_, _ string) (bool, error) {
798+
return true, nil
799+
}
800+
}
801+
}
802+
803+
// SetRecursiveNameServers to provide a custom DNS resolver.
804+
func SetRecursiveNameServers(dnsResolvers []string) {
805+
resolvers := normaliseDNSResolvers(dnsResolvers)
806+
if len(resolvers) > 0 {
807+
acme.RecursiveNameservers = resolvers
808+
log.Infof("Validating FQDN authority with DNS using %+v", resolvers)
809+
}
810+
}
811+
812+
// ensure all servers have a port number
813+
func normaliseDNSResolvers(dnsResolvers []string) []string {
814+
var normalisedResolvers []string
815+
for _, server := range dnsResolvers {
816+
srv := strings.TrimSpace(server)
817+
if len(srv) > 0 {
818+
if host, port, err := net.SplitHostPort(srv); err != nil {
819+
normalisedResolvers = append(normalisedResolvers, net.JoinHostPort(srv, "53"))
820+
} else {
821+
normalisedResolvers = append(normalisedResolvers, net.JoinHostPort(host, port))
822+
}
823+
}
824+
}
825+
return normalisedResolvers
826+
}

‎types/dns_resolvers.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package types
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// DNSResolvers is a list of DNSes that we will try to resolve the challenged FQDN against
9+
type DNSResolvers []string
10+
11+
// String is the method to format the flag's value, part of the flag.Value interface.
12+
// The String method's output will be used in diagnostics.
13+
func (r *DNSResolvers) String() string {
14+
return strings.Join(*r, ",")
15+
}
16+
17+
// Set is the method to set the flag value, part of the flag.Value interface.
18+
// Set's argument is a string to be parsed to set the flag.
19+
// It's a comma-separated list, so we split it.
20+
func (r *DNSResolvers) Set(value string) error {
21+
entryPoints := strings.Split(value, ",")
22+
if len(entryPoints) == 0 {
23+
return fmt.Errorf("wrong DNSResolvers format: %s", value)
24+
}
25+
for _, entryPoint := range entryPoints {
26+
*r = append(*r, entryPoint)
27+
}
28+
return nil
29+
}
30+
31+
// Get return the DNSResolvers list
32+
func (r *DNSResolvers) Get() interface{} {
33+
return *r
34+
}
35+
36+
// SetValue sets the DNSResolvers list
37+
func (r *DNSResolvers) SetValue(val interface{}) {
38+
*r = val.(DNSResolvers)
39+
}
40+
41+
// Type is type of the struct
42+
func (r *DNSResolvers) Type() string {
43+
return "dnsresolvers"
44+
}

0 commit comments

Comments
 (0)