1
0
Fork 0

Merge branch 'v1.7' into master

This commit is contained in:
Fernandez Ludovic 2018-08-02 17:28:44 +02:00
commit dad0e75121
38 changed files with 1171 additions and 235 deletions

View file

@ -6,12 +6,14 @@ import (
"fmt"
"io/ioutil"
fmtlog "log"
"net/url"
"reflect"
"strings"
"sync"
"time"
"github.com/BurntSushi/ty/fun"
"github.com/cenk/backoff"
"github.com/containous/flaeg/parse"
"github.com/containous/traefik/log"
"github.com/containous/traefik/rules"
@ -73,6 +75,8 @@ type Certificate struct {
type DNSChallenge struct {
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
DelayBeforeCheck parse.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
preCheckTimeout time.Duration
preCheckInterval time.Duration
}
// HTTPChallenge contains HTTP challenge Configuration
@ -130,7 +134,8 @@ func (p *Provider) Init(_ types.Constraints) error {
}
// Reset Account if caServer changed, thus registration URI can be updated
if p.account != nil && p.account.Registration != nil && !strings.HasPrefix(p.account.Registration.URI, p.CAServer) {
if p.account != nil && p.account.Registration != nil && !isAccountMatchingCaServer(p.account.Registration.URI, p.CAServer) {
log.Info("Account URI does not match the current CAServer. The account will be reset")
p.account = nil
}
@ -142,6 +147,20 @@ func (p *Provider) Init(_ types.Constraints) error {
return nil
}
func isAccountMatchingCaServer(accountURI string, serverURI string) bool {
aru, err := url.Parse(accountURI)
if err != nil {
log.Infof("Unable to parse account.Registration URL : %v", err)
return false
}
cau, err := url.Parse(serverURI)
if err != nil {
log.Infof("Unable to parse CAServer URL : %v", err)
return false
}
return cau.Hostname() == aru.Hostname()
}
// Provide allows the file provider to provide configurations to traefik
// using the given Configuration channel.
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
@ -246,6 +265,16 @@ func (p *Provider) getClient() (*acme.Client, error) {
if err != nil {
return nil, err
}
// Same default values than LEGO
p.DNSChallenge.preCheckTimeout = 60 * time.Second
p.DNSChallenge.preCheckInterval = 2 * time.Second
// Set the precheck timeout into the DNSChallenge provider
if challengeProviderTimeout, ok := provider.(acme.ChallengeProviderTimeout); ok {
p.DNSChallenge.preCheckTimeout, p.DNSChallenge.preCheckInterval = challengeProviderTimeout.Timeout()
}
} else if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 {
log.Debug("Using HTTP Challenge provider.")
@ -345,13 +374,20 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
return nil, fmt.Errorf("cannot get ACME client %v", err)
}
var certificate *acme.CertificateResource
bundle := true
certificate, err := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple)
if err != nil {
return nil, fmt.Errorf("cannot obtain certificates: %+v", err)
if p.useCertificateWithRetry(uncheckedDomains) {
certificate, err = obtainCertificateWithRetry(domains, client, p.DNSChallenge.preCheckTimeout, p.DNSChallenge.preCheckInterval, bundle)
} else {
certificate, err = client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
}
if err != nil {
return nil, fmt.Errorf("unable to generate a certificate for the domains %v: %v", uncheckedDomains, err)
}
if certificate == nil {
return nil, fmt.Errorf("domains %v do not generate a certificate", uncheckedDomains)
}
if len(certificate.Certificate) == 0 || len(certificate.PrivateKey) == 0 {
return nil, fmt.Errorf("domains %v generate certificate with no value: %v", uncheckedDomains, certificate)
}
@ -368,6 +404,60 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
return certificate, nil
}
func (p *Provider) useCertificateWithRetry(domains []string) bool {
// Check if we can use the retry mechanism only if we use the DNS Challenge and if is there are at least 2 domains to check
if p.DNSChallenge != nil && len(domains) > 1 {
rootDomain := ""
for _, searchWildcardDomain := range domains {
// Search a wildcard domain if not already found
if len(rootDomain) == 0 && strings.HasPrefix(searchWildcardDomain, "*.") {
rootDomain = strings.TrimPrefix(searchWildcardDomain, "*.")
if len(rootDomain) > 0 {
// Look for a root domain which matches the wildcard domain
for _, searchRootDomain := range domains {
if rootDomain == searchRootDomain {
// If the domains list contains a wildcard domain and its root domain, we can use the retry mechanism to obtain the certificate
return true
}
}
}
// There is only one wildcard domain in the slice, if its root domain has not been found, the retry mechanism does not have to be used
return false
}
}
}
return false
}
func obtainCertificateWithRetry(domains []string, client *acme.Client, timeout, interval time.Duration, bundle bool) (*acme.CertificateResource, error) {
var certificate *acme.CertificateResource
var err error
operation := func() error {
certificate, err = client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
return err
}
notify := func(err error, time time.Duration) {
log.Errorf("Error obtaining certificate retrying in %s", time)
}
// Define a retry backOff to let LEGO tries twice to obtain a certificate for both wildcard and root domain
ebo := backoff.NewExponentialBackOff()
ebo.MaxElapsedTime = 2 * timeout
ebo.MaxInterval = interval
rbo := backoff.WithMaxRetries(ebo, 2)
err = backoff.RetryNotify(safe.OperationWithRecover(operation), rbo, notify)
if err != nil {
log.Errorf("Error obtaining certificate: %v", err)
return nil, err
}
return certificate, nil
}
func dnsOverrideDelay(delay parse.Duration) error {
if delay == 0 {
return nil