diff --git a/.gitignore b/.gitignore
index 243850797..a74de8fb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,4 @@
*.log
*.exe
.DS_Store
-/example/acme/acme.json
+/examples/acme/acme.json
diff --git a/acme/account.go b/acme/account.go
index b73c73bc5..2e4526d75 100644
--- a/acme/account.go
+++ b/acme/account.go
@@ -24,6 +24,7 @@ type Account struct {
PrivateKey []byte
DomainsCertificate DomainsCertificates
ChallengeCerts map[string]*ChallengeCert
+ HTTPChallenge map[string]map[string][]byte
}
// ChallengeCert stores a challenge certificate
diff --git a/acme/acme.go b/acme/acme.go
index 345f6d2df..31b25d1ef 100644
--- a/acme/acme.go
+++ b/acme/acme.go
@@ -7,6 +7,8 @@ import (
"fmt"
"io/ioutil"
fmtlog "log"
+ "net"
+ "net/http"
"os"
"regexp"
"strings"
@@ -14,6 +16,8 @@ import (
"github.com/BurntSushi/ty/fun"
"github.com/cenk/backoff"
+ "github.com/containous/flaeg"
+ "github.com/containous/mux"
"github.com/containous/staert"
"github.com/containous/traefik/cluster"
"github.com/containous/traefik/log"
@@ -33,25 +37,39 @@ var (
// ACME allows to connect to lets encrypt and retrieve certs
type ACME struct {
- Email string `description:"Email address used for registration"`
- Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
- Storage string `description:"File or key used for certificates storage."`
- StorageFile string // deprecated
- OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
- OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
- CAServer string `description:"CA server to use."`
- EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
- DNSProvider string `description:"Use a DNS based challenge provider rather than HTTPS."`
- DelayDontCheckDNS int `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
- ACMELogging bool `description:"Enable debug logging of ACME actions."`
- client *acme.Client
- defaultCertificate *tls.Certificate
- store cluster.Store
- challengeProvider *challengeProvider
- checkOnDemandDomain func(domain string) bool
- jobs *channels.InfiniteChannel
- TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
- dynamicCerts *safe.Safe
+ Email string `description:"Email address used for registration"`
+ Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
+ Storage string `description:"File or key used for certificates storage."`
+ StorageFile string // deprecated
+ OnDemand bool `description:"Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated
+ OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
+ CAServer string `description:"CA server to use."`
+ EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
+ DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge"`
+ HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge"`
+ DNSProvider string `description:"Use a DNS-01 acme challenge rather than TLS-SNI-01 challenge."` // deprecated
+ DelayDontCheckDNS flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // deprecated
+ ACMELogging bool `description:"Enable debug logging of ACME actions."`
+ client *acme.Client
+ defaultCertificate *tls.Certificate
+ store cluster.Store
+ challengeTLSProvider *challengeTLSProvider
+ challengeHTTPProvider *challengeHTTPProvider
+ checkOnDemandDomain func(domain string) bool
+ jobs *channels.InfiniteChannel
+ TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
+ dynamicCerts *safe.Safe
+}
+
+// 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."`
+}
+
+// HTTPChallenge contains HTTP challenge Configuration
+type HTTPChallenge struct {
+ EntryPoint string `description:"HTTP challenge EntryPoint"`
}
//Domains parse []Domain
@@ -107,15 +125,39 @@ func (a *ACME) init() error {
return err
}
a.defaultCertificate = cert
- // TODO: to remove in the futurs
- if len(a.StorageFile) > 0 && len(a.Storage) == 0 {
- log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
- a.Storage = a.StorageFile
- }
+
a.jobs = channels.NewInfiniteChannel()
return nil
}
+// AddRoutes add routes on internal router
+func (a *ACME) AddRoutes(router *mux.Router) {
+ router.Methods(http.MethodGet).
+ Path(acme.HTTP01ChallengePath("{token}")).
+ Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ if a.challengeHTTPProvider == nil {
+ rw.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ vars := mux.Vars(req)
+ if token, ok := vars["token"]; ok {
+ domain, _, err := net.SplitHostPort(req.Host)
+ if err != nil {
+ log.Debugf("Unable to split host and port: %v. Fallback to request host.", err)
+ domain = req.Host
+ }
+ tokenValue := a.challengeHTTPProvider.getTokenValue(token, domain)
+ if len(tokenValue) > 0 {
+ rw.WriteHeader(http.StatusOK)
+ rw.Write(tokenValue)
+ return
+ }
+ }
+ rw.WriteHeader(http.StatusNotFound)
+ }))
+}
+
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, certs *safe.Safe, checkOnDemandDomain func(domain string) bool) error {
err := a.init()
@@ -155,7 +197,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
}
a.store = datastore
- a.challengeProvider = &challengeProvider{store: a.store}
+ a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
ticker := time.NewTicker(24 * time.Hour)
leadership.Pool.AddGoCtx(func(ctx context.Context) {
@@ -171,69 +213,69 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
}
})
- leadership.AddListener(func(elected bool) error {
- if elected {
- _, err := a.store.Load()
+ leadership.AddListener(a.leadershipListener)
+ return nil
+}
+
+func (a *ACME) leadershipListener(elected bool) error {
+ if elected {
+ _, err := a.store.Load()
+ if err != nil {
+ return err
+ }
+ transaction, object, err := a.store.Begin()
+ if err != nil {
+ return err
+ }
+ account := object.(*Account)
+ account.Init()
+ var needRegister bool
+ if account == nil || len(account.Email) == 0 {
+ account, err = NewAccount(a.Email)
if err != nil {
return err
}
- transaction, object, err := a.store.Begin()
+ needRegister = true
+ }
+ a.client, err = a.buildACMEClient(account)
+ if err != nil {
+ return err
+ }
+ if needRegister {
+ // New users will need to register; be sure to save it
+ log.Debug("Register...")
+ reg, err := a.client.Register()
if err != nil {
return err
}
- account := object.(*Account)
- account.Init()
- var needRegister bool
- if account == nil || len(account.Email) == 0 {
- account, err = NewAccount(a.Email)
- if err != nil {
- return err
- }
- needRegister = true
- }
+ account.Registration = reg
+ }
+ // The client has a URL to the current Let's Encrypt Subscriber
+ // Agreement. The user will need to agree to it.
+ log.Debug("AgreeToTOS...")
+ err = a.client.AgreeToTOS()
+ if err != nil {
+ log.Debug(err)
+ // Let's Encrypt Subscriber Agreement renew ?
+ reg, err := a.client.QueryRegistration()
if err != nil {
return err
}
- a.client, err = a.buildACMEClient(account)
- if err != nil {
- return err
- }
- if needRegister {
- // New users will need to register; be sure to save it
- log.Debug("Register...")
- reg, err := a.client.Register()
- if err != nil {
- return err
- }
- account.Registration = reg
- }
- // The client has a URL to the current Let's Encrypt Subscriber
- // Agreement. The user will need to agree to it.
- log.Debug("AgreeToTOS...")
+ account.Registration = reg
err = a.client.AgreeToTOS()
if err != nil {
- // Let's Encrypt Subscriber Agreement renew ?
- reg, err := a.client.QueryRegistration()
- if err != nil {
- return err
- }
- account.Registration = reg
- err = a.client.AgreeToTOS()
- if err != nil {
- log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
- }
+ log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
}
- err = transaction.Commit(account)
- if err != nil {
- return err
- }
-
- a.retrieveCertificates()
- a.renewCertificates()
- a.runJobs()
}
- return nil
- })
+ err = transaction.Commit(account)
+ if err != nil {
+ return err
+ }
+
+ a.retrieveCertificates()
+ a.renewCertificates()
+ a.runJobs()
+ }
return nil
}
@@ -253,7 +295,7 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, certs *safe.Safe, checkO
a.TLSConfig = tlsConfig
localStore := NewLocalStore(a.Storage)
a.store = localStore
- a.challengeProvider = &challengeProvider{store: a.store}
+ a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
var needRegister bool
var account *Account
@@ -337,7 +379,7 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
return providedCertificate, nil
}
- if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
+ if challengeCert, ok := a.challengeTLSProvider.getCertificate(domain); ok {
log.Debugf("ACME got challenge %s", domain)
return challengeCert, nil
}
@@ -472,16 +514,16 @@ func (a *ACME) storeRenewedCertificate(account *Account, certificateResource *Do
return nil
}
-func dnsOverrideDelay(delay int) error {
+func dnsOverrideDelay(delay flaeg.Duration) error {
var err error
if delay > 0 {
- log.Debugf("Delaying %d seconds rather than validating DNS propagation", delay)
+ log.Debugf("Delaying %d rather than validating DNS propagation", delay)
acme.PreCheckDNS = func(_, _ string) (bool, error) {
- time.Sleep(time.Duration(delay) * time.Second)
+ time.Sleep(time.Duration(delay))
return true, nil
}
} else if delay < 0 {
- err = fmt.Errorf("invalid negative DelayDontCheckDNS: %d", delay)
+ err = fmt.Errorf("invalid negative DelayBeforeCheck: %d", delay)
}
return err
}
@@ -497,25 +539,29 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
return nil, err
}
- if len(a.DNSProvider) > 0 {
- log.Debugf("Using DNS Challenge provider: %s", a.DNSProvider)
+ if a.DNSChallenge != nil && len(a.DNSChallenge.Provider) > 0 {
+ log.Debugf("Using DNS Challenge provider: %s", a.DNSChallenge.Provider)
- err = dnsOverrideDelay(a.DelayDontCheckDNS)
+ err = dnsOverrideDelay(a.DNSChallenge.DelayBeforeCheck)
if err != nil {
return nil, err
}
var provider acme.ChallengeProvider
- provider, err = dns.NewDNSChallengeProviderByName(a.DNSProvider)
+ provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider)
if err != nil {
return nil, err
}
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
err = client.SetChallengeProvider(acme.DNS01, provider)
+ } else if a.HTTPChallenge != nil && len(a.HTTPChallenge.EntryPoint) > 0 {
+ client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01})
+ a.challengeHTTPProvider = &challengeHTTPProvider{store: a.store}
+ err = client.SetChallengeProvider(acme.HTTP01, a.challengeHTTPProvider)
} else {
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
- err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
+ err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeTLSProvider)
}
if err != nil {
@@ -659,7 +705,7 @@ func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
if len(failures) > 0 {
log.Error(failures)
- return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
+ return nil, fmt.Errorf("cannot obtain certificates %+v", failures)
}
log.Debugf("Loaded ACME certificates %s", domains)
return &Certificate{
diff --git a/acme/acme_example.json b/acme/acme_example.json
new file mode 100644
index 000000000..8ebcbfd20
--- /dev/null
+++ b/acme/acme_example.json
@@ -0,0 +1,43 @@
+{
+ "Email": "test@traefik.io",
+ "Registration": {
+ "body": {
+ "resource": "reg",
+ "id": 3,
+ "key": {
+ "kty": "RSA",
+ "n": "y5a71suIqvEtovDmDVQ3SSNagk5IVCFI_TvqWpEXSrdbcDE2C-PTEtEUJuLkYwygcpiWYbPmXgdS628vQCw5Uo4DeDyHiuysJOWBLaWow3p9goOdhnPbGBq0liIR9xXyRoctdipVk8UiO9scWsu4jMBM3sMr7_yBWPfYYiLEQmZGFO3iE7Oqr55h_kncHIj5lUQY1j_jkftqxlxUB5_0quyJ7l915j5QY--eY7h4GEhRvx0TlUpi-CnRtRblGeDDDilXZD6bQN2962WdKecsmRaYx-ttLz6jCPXz2VDJRWNcIS501ne2Zh3hzw_DS6IRd2GIia1Wg4sisi9epC9sumXPHi6xzR6-_i_nsFjdtTkUcV8HmorOYoc820KQVZaLScxa8e7-ixpOd6mr6AIbEf7dBAkb9f_iK3GwpqKD8yNcaj1EQgNSyJSjnKSulXI_GwkGnuXe00Qpb1a8ha5Z8yWg7XmZZnJyAZrmK60RfwRNQ1rO5ioerNUBJ2KYTYNzVjBdob9Ug6Cjh4bEKNNjqcbjQ50_Z97Vw40xzpDQ_fYllc6n92eSuv6olxFJTmK7EhHuanDzITngaqei3zL9RwQ7P-1jfEZ03qmGrQYYqXcsS46PQ8cE-frzY2mKp16pRNCG7-03gKVGV0JHyW1aYbevNUk7OumCAXhC2YOigBk",
+ "e": "AQAB"
+ },
+ "contact": [
+ "mailto:test@traefik.io"
+ ],
+ "agreement": "http://boulder:4000/terms/v1"
+ },
+ "uri": "http://127.0.0.1:4000/acme/reg/3",
+ "new_authzr_uri": "http://127.0.0.1:4000/acme/new-authz",
+ "terms_of_service": "http://boulder:4000/terms/v1"
+ },
+ "PrivateKey": "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",
+ "DomainsCertificate": {
+ "Certs": [
+ {
+ "Domains": {
+ "Main": "local1.com",
+ "SANs": [
+ "test1.local1.com",
+ "test2.local1.com"
+ ]
+ },
+ "Certificate": {
+ "Domain": "local1.com",
+ "CertURL": "http://127.0.0.1:4000/acme/cert/ffc4f3f14def9ee6ec6a0522b5c0baa3379d",
+ "CertStableURL": "",
+ "PrivateKey": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS1FJQkFBS0NBZ0VBdVNoTTR4enF6cE5YcFNaNnAvZnQrRmt5VmgyK1BSZXJUelV0OERRSng2UkVjQS9FCnN2RnNIVmNOSkZMS2twYTNlOEd3SUZBakJQNnJPK3hoR1JjWlJrdENON1gyOW5LZFhGbHZkYzJxd0hyTFF5WWkKTTB3ODhTck41VERiNi96TWU2dTB0dERiYWtDbDd6ZEJKUXJ6a1h5ZU1MeVkzTUs3aVkrMHpwL2JqMVhvbk5DdQpaQStkZ3hsMVNrV01DVUYvQk9HNWFyT1hwb0x4S0dQWGdzV3hOTVNLVmJKSHczL3ZqNTViZU92Um5lT3BNWlhvCmMwOWpZT3VBakNka1Z5czBSWHJLNWNCRDRMbVRXdnN4MFdTK2VMVHlGTTdQTHVZM3lEWkNNWEhjVmlqRHhnbFMKYjB1ZVRQcGFUWEQwYkxqZ0RNOUVEdE15ZEJzMUNPWlpPWG9ickN5Q2I1eWxTOFdVd1NzVXM1UldxZnlVbnAvcgpSNGx2c2RZOWRVZjRPdkNMVnJvWWk5NWFGc1Zxa0xLOExuL0Eyc3kxYWlDTnR4RmpKOXRXbWU0V0NhdzRoU0YvCkR4NWVNNWNYR2JSYXduVlZJQlZXeHhzNTBPMFJlUWRvbXBQZEFNS1RDWk9SRmxYaDdOWTdxQVdWRGtpdzhyam8Kekd3Ni9XdjlOR3hTNTliKzc0YVAxcjBxOTZ2RS9Rdi8zTCtjbjhiN0lBLytPYmFKdzhIT3RGbXc4RjBxQkN3MAprYWVVSloxb1JueGFYQUo4RHhHREpFOVdNUzh0QmJtVm16YkxoRkMzeDdVc0xGeTBrSzh1SFBFT3dQb2NKNUFUCkE1UHBvclNEMmFleHA0Z3VqYVp5c1JManpmY0dnaTdva0JFNlZVNWVqRE1iYS9lNERQNEJQUVg5VmtVQ0F3RUEKQVFLQ0FnQmZjMWdYcUp1ZmZMT3REcVlpbXh4UmIrSVVKT2NpWldaSndmZDVvY244NGtEcHFDZFZ2RUZvNnF4NgpzamQ5MURhb2xOUHdCSC9aSGxRMTR3aTNQNEluQzdzS0wwTXVEeTN5SXFUa0RPOWVwSzdPWWdVMWZyTFgvS0lCCjZlc2x2Ny9HYldFTzhhSjdKdktqM0U4NEFtcEg4UDgzenJIYTlJUnJTT3NEcmNNcEpEZHpSOXp1OW1IVDZMYmYKWC9UdC9KYTNkSW42YUxUZ0FSYkRKSjAvN0J3TFFOcXpqT0dUOWdzUWRhbGdMK2x5eEo4L1ViRndhRmVwNmgzdApvbzBHcHQ0ZWgwdTdueDhlNVd3Q2RnWmJsTnpnS3grMC9Gd3dLRHhQZVRFc2ZpOEJONmlkR2NjbVdzd3prTWdtCnJmbERaeGNSWTNRSlZIVHBCL0dTTWZXRFBPQ3dRdGltQk1WN3kxM2hPMTdPWXpSNDBMZnpUalJBbmtna2V2eWYKcFowb3dLR3o4QS9haHhRWWJmYVQ5VEhXV0wrYUpYeUhFanBKckp5aTg3UExVbzhsOFVydU56MDRWNXpLOFJPbgo2cG9EWmVtbm1EYWRlU09pK3hZRWlGT1NwSXNWbzlpcm9jUGFKN2YzYWpiNUU4RHpuN1o1MmhzL2R6akpLcFZJCm5mVDFkUU9SZEowSXRUNlRlQ2RTL0dpS25IS1RtNjR2T21IbmlJcm8rUGRhUmFjV0IrTUJ0VytRd0cyUStyRGkKc3g4NlpQbHRpTVpLMDZ5TVlyVHZUdGk2aFVGaUY5cWh4b3RGazdNQkNrZlIwYUVhaUREQUpKNm1jb1lpRUQ2QgpBVGJhVmpVaGNaUiswYkRST25PN0ozRk5rZmx3K2dMaVhvcXFRRW9pU2ZWb2h5SWY3UUtDQVFFQThjYTM5K0g4CjN3L2Qrcm0yUGNhM0RMQnBYaWU4Z3ZYcGpjazVYSkpvSGVmbnJjZWQrcFpXaTZEYncwYld0MEdtYkxmVjJNSlAKV2I1aTZzSXhmdkN3YlFqbHY0UnExMVA5ZEswT3poMnVpKzZ6cXVBMG5YTVcrN0lJS0cvdDhmS2NJZGRRNnRGcwpFclFVTFBDak56ODA2cHBiSlhPRmVvMW1BK293TGhHNlA3dDhCdlZHSk1NaTNxejNlSUNuVVE2eDNFY01ITXNuClhrM21DUzI1WUZaNk96cytFK254cGVraTAzZmQwblp3UE1jdElHZys1c3hleE9zREsrTHlvb2FqQnc5N0oyUzIKcUNNWXFtT0tLcmxEQ3Y1WmQ4dlZLN3hXVmpKRVhGTTNMZ2pieHBRcCtuVXNVVWxwS01LOVlGS0lRREl0RU9aMApWcWExTXJaOElzN1l5d0tDQVFFQXhBemZIa2pIVGlvTHdZbG5EcEk0MWlOTDh5Y0ZBallrTC94dWhPU2tlVkE4CjdRWDZPZUpDekR3Z0FUYXVqOWR6Y0wwby9yTndWV0xWcnQ3OXk3YnJvVDdFREZKWVNTY25GRXNMTlVWSXRncGkKckNSUXJTL1F2TkVGTmE5K0pRc1dmYkdBNHdIUTFaSjI4MFp1cWMvNlEyUi9kZVh3cUZBQVBHN2NIcEhHWlR6ZQoyRmFRUHFLRkV4WlEyZkpvRys0SVBRNHVQVERybXlGMmVUWXk2T3BaaDBHbWJRYlVTa1dFWDlQRmF1cHJIWVdGCk8wK25DaVVPNVRaMFZoaGR2dUNKMWdPclZHYzhBUlJtUVZ1aUNEWTZCaGlvVTU0ZmZsSXlDTXZ5a3MwcmRXZ3MKWVJ2TmN4TXNlRGJpTDRKSURkMHhiN1d4VUdmVjRVNHZPMks5Vms1N0x3S0NBUUVBMkd1eE1jcXd1RnRUc0tPYwpaaUFDcXZFZTRKRmhSVGtySHlnSW1MelZSaS9ZU3M1c3MycnZmWDA0T3N5bVZ0UUZUVHdoeUMzbktjWXFkVW52ClZGblBFMHJyblV2Qzk0elBUQ205SHZPaTBzK1JORndOdlFMUWgrME5NR1ZBOFZyaU44aXRQZ1RJWU5XaFdianQKNFA1TE45V0QwVHBmT1J4cFBRZmNxT0JsZjdjcmhtNzNvdUNwemZtMmE3OStCaWpKUFF5NzR1cFhDeXRmeHNlUApNSlU0Uk56NjdJaDFMclpKM2xGbDFvYitZT2xKazhDOHpZd1RLT0hWck9zeGxobyt4SXN2Q2t3MDFMelZ6Mi9hCnRmT3Y5NTlHSnQzbXE0ZWpJUFZPQy9iUlpmdTMvMEdSY2dpQTZ5SnpaM0VxWTVaOU1EbTU3VzdjcE5RRlRxZmEKNXEyUmtRS0NBUUErNGhZSzQ3TXg2aUNkTWxKaEJSdS82OUJucktOWm96NFdPalRFNFlXejk3MmpGU0Mrd2tsRQpzeUJjNDBvNGp4WFRHb2wwc04rZU03WndnY3dNTko3OXVHRXZ4cFhVMlA4YTdqc3BHaEVKZXVsTlo5U015R0orCnZkaWE4TEJZZDJiK2FCbjhOay9pd1Rqd0xTNC92NXI1Vk5uaFdpRElDK2tYZVVPWGRwQ1pWbDN3TEV2V0cxRHQKMzJHTmxzZzM5VENsVE5BZUJudjc1VTdYOEQrQ0gvRVpoa0E0aGxFL2hXN0JRZTczclRzd1creHhLc3BjWWFpVwpjdEg3NzVMYUw3Rm1lUVRTYk01OVZpcTZXZ2J0OVY3Rko5R09DSkQzZHF2ZjBITDlEVndjSzQ3WWt3OWlFc3RYCnY5cnEvREhhYUpGNzBGNlFlTTNNbDhSa212WTZJYkEzQW9JQkFRRGt6RmZLeG9HQ3dWUDlua3k4NmFQSjFvd2kKc2FDZEx6RjRWTENRZzkrUXJITzEyY0p5MFFQUnJ2cUQyMGp1cDFlOWJhWVZzbkdYc1FZTFg2NVR6UzJSSCtlSAp6S0NPTTdnMVE3djMxNWpjMDMvN1lQck4rb3RrV0VBOUkyaDZjUE1vY3c0aERTNk02OFlxQVlKTS9RclVhenZhCnhBTFJaZEVkQW1xWDA4VHhuY1hRUEVxYkk0ZnlSZ2pVM1BYR3RRaFFFbERpR2kwbThjQTJNTXdsR1RmbTdOSXgKaENjZ2ZkL296TEp2VUhiMkxLRi82cXEySmJVRHlOMkVoK0xSZUJjdnp6Y1grZE5MdGQxY0Uvcm1SM2hMbWxmNgo3KzRpTVMxK0t1eWV3VlJVUEE1c1F1aUYyVUVoeEs1MUpZK1FpOG9HbERKdGRrOXB3QlZNN1F0WW9KVEwKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K",
+ "Certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZvakNDQklxZ0F3SUJBZ0lUQVAvRTgvRk43NTdtN0dvRklyWEF1cU0zblRBTkJna3Foa2lHOXcwQkFRc0YKQURBZk1SMHdHd1lEVlFRRERCUm9NbkJ3ZVNCb01tTnJaWElnWm1GclpTQkRRVEFlRncweE9EQXhNVFV3TnpJNQpNREJhRncweE9EQTBNVFV3TnpJNU1EQmFNRVF4RXpBUkJnTlZCQU1UQ214dlkyRnNNUzVqYjIweExUQXJCZ05WCkJBVVRKR1ptWXpSbU0yWXhOR1JsWmpsbFpUWmxZelpoTURVeU1tSTFZekJpWVdFek16YzVaRENDQWlJd0RRWUoKS29aSWh2Y05BUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTGtvVE9NYzZzNlRWNlVtZXFmMzdmaFpNbFlkdmowWApxMDgxTGZBMENjZWtSSEFQeExMeGJCMVhEU1JTeXBLV3QzdkJzQ0JRSXdUK3F6dnNZUmtYR1VaTFFqZTE5dlp5Cm5WeFpiM1hOcXNCNnkwTW1Jak5NUFBFcXplVXcyK3Y4ekh1cnRMYlEyMnBBcGU4M1FTVUs4NUY4bmpDOG1OekMKdTRtUHRNNmYyNDlWNkp6UXJtUVBuWU1aZFVwRmpBbEJmd1RodVdxemw2YUM4U2hqMTRMRnNUVEVpbFd5UjhOLwo3NCtlVzNqcjBaM2pxVEdWNkhOUFkyRHJnSXduWkZjck5FVjZ5dVhBUStDNWsxcjdNZEZrdm5pMDhoVE96eTdtCk44ZzJRakZ4M0ZZb3c4WUpVbTlMbmt6NldrMXc5R3k0NEF6UFJBN1RNblFiTlFqbVdUbDZHNndzZ20rY3BVdkYKbE1FckZMT1VWcW44bEo2ZjYwZUpiN0hXUFhWSCtEcndpMWE2R0l2ZVdoYkZhcEN5dkM1L3dOck10V29namJjUgpZeWZiVnBudUZnbXNPSVVoZnc4ZVhqT1hGeG0wV3NKMVZTQVZWc2NiT2REdEVYa0hhSnFUM1FEQ2t3bVRrUlpWCjRleldPNmdGbFE1SXNQSzQ2TXhzT3Yxci9UUnNVdWZXL3UrR2o5YTlLdmVyeFAwTC85eS9uSi9HK3lBUC9qbTIKaWNQQnpyUlpzUEJkS2dRc05KR25sQ1dkYUVaOFdsd0NmQThSZ3lSUFZqRXZMUVc1bFpzMnk0UlF0OGUxTEN4Ywp0SkN2TGh6eERzRDZIQ2VRRXdPVDZhSzBnOW1uc2FlSUxvMm1jckVTNDgzM0JvSXU2SkFST2xWT1hvd3pHMnYzCnVBeitBVDBGL1ZaRkFnTUJBQUdqZ2dHd01JSUJyREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdIUVlEVlIwbEJCWXcKRkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQ01Bd0dBMVVkRXdFQi93UUNNQUF3SFFZRFZSME9CQllFRk5LZQpBVUZYc2Z2N2lML0lYVVBXdzY2ZU5jQnhNQjhHQTFVZEl3UVlNQmFBRlB0NFR4TDVZQldETEo4WGZ6UVpzeTQyCjZrR0pNR1lHQ0NzR0FRVUZCd0VCQkZvd1dEQWlCZ2dyQmdFRkJRY3dBWVlXYUhSMGNEb3ZMekV5Tnk0d0xqQXUKTVRvME1EQXlMekF5QmdnckJnRUZCUWN3QW9ZbWFIUjBjRG92THpFeU55NHdMakF1TVRvME1EQXdMMkZqYldVdgphWE56ZFdWeUxXTmxjblF3T1FZRFZSMFJCREl3TUlJS2JHOWpZV3d4TG1OdmJZSVFkR1Z6ZERFdWJHOWpZV3d4CkxtTnZiWUlRZEdWemRESXViRzlqWVd3eExtTnZiVEFuQmdOVkhSOEVJREFlTUJ5Z0dxQVloaFpvZEhSd09pOHYKWlhoaGJYQnNaUzVqYjIwdlkzSnNNR0VHQTFVZElBUmFNRmd3Q0FZR1o0RU1BUUlCTUV3R0F5b0RCREJGTUNJRwpDQ3NHQVFVRkJ3SUJGaFpvZEhSd09pOHZaWGhoYlhCc1pTNWpiMjB2WTNCek1COEdDQ3NHQVFVRkJ3SUNNQk1NCkVVUnZJRmRvWVhRZ1ZHaHZkU0JYYVd4ME1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ3A0Q2FxZlR4THNQTzQKS2JueDJZdEc4bTN3MC9keTVVR1VRNjZHbGxPVTk0L2I0MmNhbTRuNUZrTWlpZ01IaUx4c2JZVXh0cDZKQ3R5cQpLKzFNcDFWWEtSTTVKbFBTNWRIaWhxdHk1U3BrTUhjampwQSs3U2YyVWtoNmpKRWYxTUVJY2JnWnpJRk5IT0hYClVUUUppVFhKcno3blJDZnlQWFZtbWErUGtIRlU4R0VEVzJGOVptU1kzVFBiQWhiWkV2UkZubjUrR1lxbkZuancKWWw3Y0I2MXYwRzVpOGQwbnVvbTB4a2hiNTU3Y3BiZHhLblhsaFU4N2RZSTR5SUdPdUFGUWpYcXFXN2NIZCtXUQpWSDB2dFA3cEgrRmt2YnY4WkkxMHMrNU5ZcCtzZjFQZGQxekJsRmdNSGF3dnFFYUg3SU9sejdkajlCdmtVc0dpClhxQWVqQnFPCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVpakNDQTNLZ0F3SUJBZ0lDRWswd0RRWUpLb1pJaHZjTkFRRUxCUUF3S3pFcE1DY0dBMVVFQXd3Z1kyRmoKYTJ4cGJtY2dZM0o1Y0hSdlozSmhjR2hsY2lCbVlXdGxJRkpQVDFRd0hoY05NVFV4TURJeE1qQXhNVFV5V2hjTgpNakF4TURFNU1qQXhNVFV5V2pBZk1SMHdHd1lEVlFRREV4Um9ZWEJ3ZVNCb1lXTnJaWElnWm1GclpTQkRRVENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUlLUjNtYUJjVVNzbmNYWXpRVDEzRDUKTnIrWjNtTHhNTWgzVFVkdDZzQUNtcWJKMGJ0UmxnWGZNdE5MTTJPVTFJNmEzSnUrdElaU2RuMnYyMUpCd3Z4VQp6cFpRNHp5MmNpbUlpTVFEWkNRSEp3ekM5R1puOEhhVzA5MWl6OUgwR28zQTdXRFh3WU5tc2RMTlJpMDBvMTRVCmpvYVZxYVBzWXJaV3ZSS2FJUnFhVTBoSG1TMEFXd1FTdk4vOTNpTUlYdXlpd3l3bWt3S2JXbm54Q1EvZ3NjdEsKRlV0Y05yd0V4OVdnajZLbGh3RFR5STFRV1NCYnhWWU55VWdQRnpLeHJTbXdNTzB5TmZmN2hvK1FUOXg1K1kvNwpYRTU5UzRNYzRaWHhjWEtldy9nU2xOOVU1bXZUK0QyQmhEdGtDdXBkZnNaTkNRV3AyN0ErYi9EbXJGSTlOcXNDCkF3RUFBYU9DQWNJd2dnRytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3UXdZRFZSMGVCRHd3T3FFNE1BYUMKQkM1dGFXd3dDb2NJQUFBQUFBQUFBQUF3SW9jZ0FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpBQUFBQUFBd0RnWURWUjBQQVFIL0JBUURBZ0dHTUg4R0NDc0dBUVVGQndFQkJITXdjVEF5QmdnckJnRUZCUWN3CkFZWW1hSFIwY0RvdkwybHpjbWN1ZEhKMWMzUnBaQzV2WTNOd0xtbGtaVzUwY25WemRDNWpiMjB3T3dZSUt3WUIKQlFVSE1BS0dMMmgwZEhBNkx5OWhjSEJ6TG1sa1pXNTBjblZ6ZEM1amIyMHZjbTl2ZEhNdlpITjBjbTl2ZEdOaAplRE11Y0Rkak1COEdBMVVkSXdRWU1CYUFGT21rUCs2ZXBlYnkxZGQ1WUR5VHBpNGtqcGVxTUZRR0ExVWRJQVJOCk1Fc3dDQVlHWjRFTUFRSUJNRDhHQ3lzR0FRUUJndDhUQVFFQk1EQXdMZ1lJS3dZQkJRVUhBZ0VXSW1oMGRIQTYKTHk5amNITXVjbTl2ZEMxNE1TNXNaWFJ6Wlc1amNubHdkQzV2Y21jd1BBWURWUjBmQkRVd016QXhvQytnTFlZcgphSFIwY0RvdkwyTnliQzVwWkdWdWRISjFjM1F1WTI5dEwwUlRWRkpQVDFSRFFWZ3pRMUpNTG1OeWJEQWRCZ05WCkhRNEVGZ1FVKzNoUEV2bGdGWU1zbnhkL05CbXpMamJxUVlrd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFBMFkKQWVMWE9rbHg0aGhDaWtVVWwrQmRuRmZuMWcwVzVBaVFMVk5JT0w2UG5xWHUwd2puaE55aHFkd25maFlNbm95NAppZFJoNGxCNnB6OEdmOXBubExkL0RuV1NWM2dTKy9JL21BbDFkQ2tLYnk2SDJWNzkwZTZJSG1JSzJLWW0zam0rClUrK0ZJZEdwQmRzUVRTZG1pWC9yQXl1eE1ETTBhZE1rTkJ3VGZRbVpRQ3o2bkdIdzFRY1NQWk12WnBzQzhTa3YKZWt6eHNqRjFvdE9yTVVQTlBRdnRUV3JWeDhHbFIycWZ4LzR4YlFhMXYyZnJOdkZCQ21PNTlnb3oram5XdmZUdApqMk5qd0RaN3ZsTUJzUG0xNmRiS1lDODQwdXZSb1pqeHFzZGMzQ2hDWmpxaW1GcWxORy94b1BBOCtkVGljWnpDClhFOWlqUEljdlc2eTFhYTNiR3c9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
+ }
+ }
+ ]
+ },
+ "ChallengeCerts": {}
+}
diff --git a/acme/acme_test.go b/acme/acme_test.go
index 272e2571d..3498e7b9f 100644
--- a/acme/acme_test.go
+++ b/acme/acme_test.go
@@ -267,7 +267,7 @@ cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`)
}`))
}))
defer ts.Close()
- a := ACME{DNSProvider: "manual", DelayDontCheckDNS: 10, CAServer: ts.URL}
+ a := ACME{DNSChallenge: &DNSChallenge{Provider: "manual", DelayBeforeCheck: 10}, CAServer: ts.URL}
client, err := a.buildACMEClient(account)
if err != nil {
diff --git a/acme/challenge_http_provider.go b/acme/challenge_http_provider.go
new file mode 100644
index 000000000..901fa5594
--- /dev/null
+++ b/acme/challenge_http_provider.go
@@ -0,0 +1,92 @@
+package acme
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/cenk/backoff"
+ "github.com/containous/traefik/cluster"
+ "github.com/containous/traefik/log"
+ "github.com/containous/traefik/safe"
+ "github.com/xenolf/lego/acme"
+)
+
+var _ acme.ChallengeProviderTimeout = (*challengeHTTPProvider)(nil)
+
+type challengeHTTPProvider struct {
+ store cluster.Store
+ lock sync.RWMutex
+}
+
+func (c *challengeHTTPProvider) getTokenValue(token, domain string) []byte {
+ log.Debugf("Looking for an existing ACME challenge for token %v...", token)
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ account := c.store.Get().(*Account)
+ if account.HTTPChallenge == nil {
+ return []byte{}
+ }
+ var result []byte
+ operation := func() error {
+ var ok bool
+ if result, ok = account.HTTPChallenge[token][domain]; !ok {
+ return fmt.Errorf("cannot find challenge for token %v", token)
+ }
+ return nil
+ }
+ notify := func(err error, time time.Duration) {
+ log.Errorf("Error getting challenge for token retrying in %s", time)
+ }
+ ebo := backoff.NewExponentialBackOff()
+ ebo.MaxElapsedTime = 60 * time.Second
+ err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
+ if err != nil {
+ log.Errorf("Error getting challenge for token: %v", err)
+ return []byte{}
+ }
+ return result
+}
+
+func (c *challengeHTTPProvider) Present(domain, token, keyAuth string) error {
+ log.Debugf("Challenge Present %s", domain)
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ transaction, object, err := c.store.Begin()
+ if err != nil {
+ return err
+ }
+ account := object.(*Account)
+ if account.HTTPChallenge == nil {
+ account.HTTPChallenge = map[string]map[string][]byte{}
+ }
+ if _, ok := account.HTTPChallenge[token]; !ok {
+ account.HTTPChallenge[token] = map[string][]byte{}
+ }
+ account.HTTPChallenge[token][domain] = []byte(keyAuth)
+ return transaction.Commit(account)
+}
+
+func (c *challengeHTTPProvider) CleanUp(domain, token, keyAuth string) error {
+ log.Debugf("Challenge CleanUp %s", domain)
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ transaction, object, err := c.store.Begin()
+ if err != nil {
+ return err
+ }
+ account := object.(*Account)
+ if _, ok := account.HTTPChallenge[token]; ok {
+ if _, domainOk := account.HTTPChallenge[token][domain]; domainOk {
+ delete(account.HTTPChallenge[token], domain)
+ }
+ if len(account.HTTPChallenge[token]) == 0 {
+ delete(account.HTTPChallenge, token)
+ }
+ }
+ return transaction.Commit(account)
+}
+
+func (c *challengeHTTPProvider) Timeout() (timeout, interval time.Duration) {
+ return 60 * time.Second, 5 * time.Second
+}
diff --git a/acme/challengeProvider.go b/acme/challenge_tls_provider.go
similarity index 89%
rename from acme/challengeProvider.go
rename to acme/challenge_tls_provider.go
index 95c032434..9235f690f 100644
--- a/acme/challengeProvider.go
+++ b/acme/challenge_tls_provider.go
@@ -23,14 +23,14 @@ import (
"github.com/xenolf/lego/acme"
)
-var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
+var _ acme.ChallengeProviderTimeout = (*challengeTLSProvider)(nil)
-type challengeProvider struct {
+type challengeTLSProvider struct {
store cluster.Store
lock sync.RWMutex
}
-func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
+func (c *challengeTLSProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
log.Debugf("Looking for an existing ACME challenge for %s...", domain)
if !strings.HasSuffix(domain, ".acme.invalid") {
return nil, false
@@ -67,7 +67,7 @@ func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate
return result, true
}
-func (c *challengeProvider) Present(domain, token, keyAuth string) error {
+func (c *challengeTLSProvider) Present(domain, token, keyAuth string) error {
log.Debugf("Challenge Present %s", domain)
cert, _, err := tlsSNI01ChallengeCert(keyAuth)
if err != nil {
@@ -88,7 +88,7 @@ func (c *challengeProvider) Present(domain, token, keyAuth string) error {
return transaction.Commit(account)
}
-func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
+func (c *challengeTLSProvider) CleanUp(domain, token, keyAuth string) error {
log.Debugf("Challenge CleanUp %s", domain)
c.lock.Lock()
defer c.lock.Unlock()
@@ -101,7 +101,7 @@ func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
return transaction.Commit(account)
}
-func (c *challengeProvider) Timeout() (timeout, interval time.Duration) {
+func (c *challengeTLSProvider) Timeout() (timeout, interval time.Duration) {
return 60 * time.Second, 5 * time.Second
}
diff --git a/acme/localStore_test.go b/acme/localStore_test.go
new file mode 100644
index 000000000..a2bfce742
--- /dev/null
+++ b/acme/localStore_test.go
@@ -0,0 +1,41 @@
+package acme
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestLoad(t *testing.T) {
+ acmeFile := "./acme_example.json"
+
+ folder, prefix := filepath.Split(acmeFile)
+ tmpFile, err := ioutil.TempFile(folder, prefix)
+ defer os.Remove(tmpFile.Name())
+
+ if err != nil {
+ t.Error(err)
+ }
+
+ fileContent, err := ioutil.ReadFile(acmeFile)
+ if err != nil {
+ t.Error(err)
+ }
+
+ tmpFile.Write(fileContent)
+
+ localStore := NewLocalStore(tmpFile.Name())
+ obj, err := localStore.Load()
+ if err != nil {
+ t.Error(err)
+ }
+ account, ok := obj.(*Account)
+ if !ok {
+ t.Error("Object is not an ACME Account")
+ }
+
+ if len(account.DomainsCertificate.Certs) != 1 {
+ t.Errorf("Must found %d and found %d certificates in Account", 3, len(account.DomainsCertificate.Certs))
+ }
+}
diff --git a/cmd/traefik/anonymize/anonymize_config_test.go b/cmd/traefik/anonymize/anonymize_config_test.go
index abfd4732a..2f9571e7c 100644
--- a/cmd/traefik/anonymize/anonymize_config_test.go
+++ b/cmd/traefik/anonymize/anonymize_config_test.go
@@ -168,7 +168,7 @@ func TestDo_globalConfiguration(t *testing.T) {
OnHostRule: true,
CAServer: "CAServer",
EntryPoint: "EntryPoint",
- DNSProvider: "DNSProvider",
+ DNSChallenge: &acme.DNSChallenge{Provider: "DNSProvider"},
DelayDontCheckDNS: 666,
ACMELogging: true,
TLSConfig: &tls.Config{
diff --git a/configuration/configuration.go b/configuration/configuration.go
index bc9a1816a..3805e4431 100644
--- a/configuration/configuration.go
+++ b/configuration/configuration.go
@@ -240,6 +240,23 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
log.Errorln("Error using file configuration backend, no filename defined")
}
}
+
+ if gc.ACME != nil {
+ // TODO: to remove in the futurs
+ if len(gc.ACME.StorageFile) > 0 && len(gc.ACME.Storage) == 0 {
+ log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
+ gc.ACME.Storage = gc.ACME.StorageFile
+ }
+
+ if len(gc.ACME.DNSProvider) > 0 {
+ log.Warn("ACME.DNSProvider is deprecated, use ACME.DNSChallenge instead")
+ gc.ACME.DNSChallenge = &acme.DNSChallenge{Provider: gc.ACME.DNSProvider, DelayBeforeCheck: gc.ACME.DelayDontCheckDNS}
+ }
+
+ if gc.ACME.OnDemand {
+ log.Warn("ACME.OnDemand is deprecated")
+ }
+ }
}
// DefaultEntryPoints holds default entry points
diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md
index 05a105c1c..53cd3cae7 100644
--- a/docs/configuration/acme.md
+++ b/docs/configuration/acme.md
@@ -7,10 +7,14 @@ See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) an
```toml
# Sample entrypoint configuration when using ACME.
[entryPoints]
+ [entryPoints.http]
+ address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
+```
+```toml
# Enable ACME (Let's Encrypt): automatic SSL.
[acme]
@@ -33,17 +37,16 @@ email = "test@traefik.io"
storage = "acme.json"
# or `storage = "traefik/acme/account"` if using KV store.
-# Entrypoint to proxy acme challenge/apply certificates to.
-# WARNING, must point to an entrypoint on port 443
+# Entrypoint to proxy acme apply certificates to.
+# WARNING, if the TLS-SNI-01 challenge is used, it must point to an entrypoint on port 443
#
# Required
#
entryPoint = "https"
-# Use a DNS based acme challenge rather than external HTTPS access
+# Use a DNS-01 acme challenge rather than TLS-SNI-01 challenge
#
-#
-# Optional
+# Optional (Deprecated, replaced by [acme.dnsChallenge])
#
# dnsProvider = "digitalocean"
@@ -51,25 +54,29 @@ entryPoint = "https"
# If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds.
# Useful if internal networks block external DNS queries.
#
-# Optional
+# Optional (Deprecated, replaced by [acme.dnsChallenge])
+# Default: 0
#
# delayDontCheckDNS = 0
# If true, display debug log messages from the acme client library.
#
# Optional
+# Default: false
#
# acmeLogging = true
-# Enable on demand certificate. (Deprecated)
+# Enable on demand certificate generation.
#
-# Optional
+# Optional (Deprecated)
+# Default: false
#
# onDemand = true
# Enable certificate generation on frontends Host rules.
#
# Optional
+# Default: false
#
# onHostRule = true
@@ -78,26 +85,64 @@ entryPoint = "https"
# - Leave comment to go to prod.
#
# Optional
+# Default: "https://acme-v01.api.letsencrypt.org/directory"
#
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
# Domains list.
#
# [[acme.domains]]
-# main = "local1.com"
-# sans = ["test1.local1.com", "test2.local1.com"]
+# main = "local1.com"
+# sans = ["test1.local1.com", "test2.local1.com"]
# [[acme.domains]]
-# main = "local2.com"
-# sans = ["test1.local2.com", "test2.local2.com"]
+# main = "local2.com"
+# sans = ["test1.local2.com", "test2.local2.com"]
# [[acme.domains]]
-# main = "local3.com"
+# main = "local3.com"
# [[acme.domains]]
-# main = "local4.com"
+# main = "local4.com"
+
+# Use a HTTP-01 acme challenge rather than TLS-SNI-01 challenge
+#
+# Optional but recommend
+#
+[acme.httpChallenge]
+
+ # EntryPoint to use for the challenges.
+ #
+ # Required
+ #
+ entryPoint = "http"
+
+# Use a DNS-01 acme challenge rather than TLS-SNI-01 challenge
+#
+# Optional
+#
+# [acme.dnsChallenge]
+
+ # Provider used.
+ #
+ # Required
+ #
+ # provider = "digitalocean"
+
+ # By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
+ # If delayBeforeCheck is greater than zero, avoid this & instead just wait so many seconds.
+ # Useful if internal networks block external DNS queries.
+ #
+ # Optional
+ # Default: 0
+ #
+ # delayBeforeCheck = 0
```
+!!! note
+ Even if `TLS-SNI-01` challenge is [disabled](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188) for the moment, it stays the _by default_ ACME Challenge in Træfik.
+ If `TLS-SNI-01` challenge is not re-enabled in the future, it we will be removed from Træfik.
!!! note
- ACME entryPoint has to be relied to the port 443, otherwise ACME Challenges can not be done.
- It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
+ If `TLS-SNI-01` challenge is used, `acme.entryPoint` has to be reachable by Let's Encrypt through the port 443.
+ If `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through the port 80.
+ These are Let's Encrypt limitations as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
### `storage`
@@ -110,7 +155,7 @@ storage = "acme.json"
File or key used for certificates storage.
-**WARNING** If you use Træfik in Docker, you have 2 options:
+**WARNING:** If you use Træfik in Docker, you have 2 options:
- create a file on your host and mount it as a volume:
```toml
@@ -133,19 +178,60 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
!!! note
During Træfik configuration migration from a configuration file to a KV store (thanks to `storeconfig` subcommand as described [here](/user-guide/kv-config/#store-configuration-in-key-value-store)), if ACME certificates have to be migrated too, use both `storageFile` and `storage`.
- `storageFile` will contain the path to the `acme.json` file to migrate.
- `storage` will contain the key where the certificates will be stored.
-### `dnsProvider`
+ - `storageFile` will contain the path to the `acme.json` file to migrate.
+ - `storage` will contain the key where the certificates will be stored.
+
+### `acme.httpChallenge`
+
+Use `HTTP-01` challenge to generate/renew ACME certificates.
```toml
[acme]
# ...
-dnsProvider = "digitalocean"
+entryPoint = "https"
+[acme.httpChallenge]
+ entryPoint = "http"
+```
+
+#### `entryPoint`
+
+Specify the entryPoint to use during the challenges.
+
+```toml
+[entryPoints]
+ [entryPoints.http]
+ address = ":80"
+ [entryPoints.https]
+ address = ":443"
+ [entryPoints.https.tls]
+# ...
+
+[acme]
+ # ...
+ entryPoint = "https"
+ [acme.httpChallenge]
+ entryPoint = "http"
+```
+
+!!! note
+ `acme.httpChallenge.entryPoint` has to be reachable by Let's Encrypt through the port 80.
+ It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
+
+### `acme.dnsChallenge`
+
+Use `DNS-01` challenge to generate/renew ACME certificates.
+
+```toml
+[acme]
+# ...
+[acme.dnsChallenge]
+ provider = "digitalocean"
+ delayBeforeCheck = 0
# ...
```
-Use a DNS based acme challenge rather than external HTTPS access, e.g. for a firewalled server.
+#### `provider`
Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables to enable setting it:
@@ -164,7 +250,7 @@ Select the provider that matches the DNS domain that will host the challenge TXT
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` |
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` |
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` |
-| manual | - | none, but run Træfik interactively & turn on `acmeLogging` to see instructions & press Enter. |
+| manual | - | none, but run Træfik interactively & turn on `acmeLogging` to see instructions & press Enter. |
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` |
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` |
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` |
@@ -175,22 +261,21 @@ Select the provider that matches the DNS domain that will host the challenge TXT
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or configured user/instance IAM profile. |
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` |
-### `delayDontCheckDNS`
+#### `delayBeforeCheck`
-```toml
-[acme]
-# ...
-delayDontCheckDNS = 0
-# ...
-```
-
-By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify.
-If `delayDontCheckDNS` is greater than zero, avoid this & instead just wait so many seconds.
+By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
+If `delayBeforeCheck` is greater than zero, avoid this & instead just wait so many seconds.
Useful if internal networks block external DNS queries.
+!!! note
+ This field has no sense if a `provider` is not defined.
+
### `onDemand` (Deprecated)
+!!! warning
+ This option is deprecated.
+
```toml
[acme]
# ...
@@ -208,9 +293,6 @@ This will request a certificate from Let's Encrypt during the first TLS handshak
!!! warning
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
-!!! warning
- This option is deprecated.
-
### `onHostRule`
```toml
@@ -240,21 +322,21 @@ CA server to use.
- Uncomment the line to run on the staging Let's Encrypt server.
- Leave comment to go to prod.
-### `domains`
+### `acme.domains`
```toml
[acme]
# ...
[[acme.domains]]
-main = "local1.com"
-sans = ["test1.local1.com", "test2.local1.com"]
+ main = "local1.com"
+ sans = ["test1.local1.com", "test2.local1.com"]
[[acme.domains]]
-main = "local2.com"
-sans = ["test1.local2.com", "test2.local2.com"]
+ main = "local2.com"
+ sans = ["test1.local2.com", "test2.local2.com"]
[[acme.domains]]
-main = "local3.com"
+ main = "local3.com"
[[acme.domains]]
-main = "local4.com"
+ main = "local4.com"
# ...
```
@@ -265,3 +347,15 @@ All domains must have A/AAAA records pointing to Træfik.
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
Each domain & SANs will lead to a certificate request.
+
+### `dnsProvider` (Deprecated)
+
+!!! warning
+ This option is deprecated.
+ Please refer to [DNS challenge provider section](/configuration/acme/#provider)
+
+### `delayDontCheckDNS` (Deprecated)
+
+!!! warning
+ This option is deprecated.
+ Please refer to [DNS challenge delayBeforeCheck section](/configuration/acme/#delaybeforecheck)
diff --git a/docs/user-guide/cluster-docker-consul.md b/docs/user-guide/cluster-docker-consul.md
index 2bc3eb2df..48c5de9e1 100644
--- a/docs/user-guide/cluster-docker-consul.md
+++ b/docs/user-guide/cluster-docker-consul.md
@@ -56,15 +56,18 @@ $ traefik \
--acme \
--acme.storage=/etc/traefik/acme/acme.json \
--acme.entryPoint=https \
+ --acme.httpChallenge.entryPoint=http \
--acme.email=contact@mydomain.ca
```
-Let's Encrypt needs 3 parameters: an entry point to listen to, a storage for certificates, and an email for the registration.
+Let's Encrypt needs 4 parameters: an TLS entry point to listen to, a non-TLS entry point to allow HTTP challenges, a storage for certificates, and an email for the registration.
To enable Let's Encrypt support, you need to add `--acme` flag.
Now, Træfik needs to know where to store the certificates, we can choose between a key in a Key-Value store, or a file path: `--acme.storage=my/key` or `--acme.storage=/path/to/acme.json`.
+The `acme.httpChallenge.entryPoint` flag enables the `HTTP-01` challenge and specifies the entryPoint to use during the challenges.
+
For your email and the entry point, it's `--acme.entryPoint` and `--acme.email` flags.
### Docker configuration
@@ -97,6 +100,7 @@ services:
- "--acme"
- "--acme.storage=/etc/traefik/acme/acme.json"
- "--acme.entryPoint=https"
+ - "--acme.httpChallenge.entryPoint=http"
- "--acme.OnHostRule=true"
- "--acme.onDemand=false"
- "--acme.email=contact@mydomain.ca"
@@ -206,12 +210,13 @@ services:
- "--acme"
- "--acme.storage=traefik/acme/account"
- "--acme.entryPoint=https"
+ - "--acme.httpChallenge.entryPoint=http"
- "--acme.OnHostRule=true"
- "--acme.onDemand=false"
- - "--acme.email=contact@jmaitrehenry.ca"
+ - "--acme.email=foobar@example.com"
- "--docker"
- "--docker.swarmmode"
- - "--docker.domain=jmaitrehenry.ca"
+ - "--docker.domain=example.com"
- "--docker.watch"
- "--consul"
- "--consul.endpoint=consul:8500"
diff --git a/docs/user-guide/docker-and-lets-encrypt.md b/docs/user-guide/docker-and-lets-encrypt.md
index 24c149016..cca881979 100644
--- a/docs/user-guide/docker-and-lets-encrypt.md
+++ b/docs/user-guide/docker-and-lets-encrypt.md
@@ -104,6 +104,8 @@ email = "your-email-here@my-awesome-app.org"
storage = "acme.json"
entryPoint = "https"
OnHostRule = true
+[acme.httpChallenge]
+entryPoint = "http"
```
This is the minimum configuration required to do the following:
diff --git a/docs/user-guide/examples.md b/docs/user-guide/examples.md
index 924e88dd6..ff2813a6c 100644
--- a/docs/user-guide/examples.md
+++ b/docs/user-guide/examples.md
@@ -6,6 +6,7 @@ You will find here some configuration examples of Træfik.
```toml
defaultEntryPoints = ["http"]
+
[entryPoints]
[entryPoints.http]
address = ":80"
@@ -15,6 +16,7 @@ defaultEntryPoints = ["http"]
```toml
defaultEntryPoints = ["http", "https"]
+
[entryPoints]
[entryPoints.http]
address = ":80"
@@ -34,6 +36,7 @@ Note that we can either give path to certificate file or directly the file conte
```toml
defaultEntryPoints = ["http", "https"]
+
[entryPoints]
[entryPoints.http]
address = ":80"
@@ -52,10 +55,16 @@ defaultEntryPoints = ["http", "https"]
## Let's Encrypt support
-### Basic example
+!!! note
+ Even if `TLS-SNI-01` challenge is [disabled](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188), for the moment, it stays the _by default_ ACME Challenge in Træfik but all the examples use the `HTTP-01` challenge (except DNS challenge examples).
+ If `TLS-SNI-01` challenge is not re-enabled in the future, it we will be removed from Træfik.
+
+### Basic example with HTTP challenge
```toml
[entryPoints]
+ [entryPoints.http]
+ address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@@ -65,6 +74,8 @@ email = "test@traefik.io"
storage = "acme.json"
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
+ [acme.httpChallenge]
+ entryPoint = "http"
[[acme.domains]]
main = "local1.com"
@@ -78,14 +89,16 @@ entryPoint = "https"
main = "local4.com"
```
-This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com` with described SANs.
+This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com` with described SANs.
Traefik generates these certificates when it starts and it needs to be restart if new domains are added.
-### OnHostRule option
+### OnHostRule option (with HTTP challenge)
```toml
[entryPoints]
+ [entryPoints.http]
+ address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@@ -96,6 +109,8 @@ storage = "acme.json"
onHostRule = true
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
+ [acme.httpChallenge]
+ entryPoint = "http"
[[acme.domains]]
main = "local1.com"
@@ -109,16 +124,18 @@ entryPoint = "https"
main = "local4.com"
```
-This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com`.
+This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com`.
Traefik generates these certificates when it starts.
If a backend is added with a `onHost` rule, Traefik will automatically generate the Let's Encrypt certificate for the new domain.
-### OnDemand option
+### OnDemand option (with HTTP challenge)
```toml
[entryPoints]
+ [entryPoints.http]
+ address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@@ -129,9 +146,11 @@ storage = "acme.json"
onDemand = true
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
+ [acme.httpChallenge]
+ entryPoint = "http"
```
-This configuration allows generating a Let's Encrypt certificate during the first HTTPS request on a new domain.
+This configuration allows generating a Let's Encrypt certificate (thanks to `HTTP-01` challenge) during the first HTTPS request on a new domain.
!!! note
@@ -153,10 +172,11 @@ This configuration allows generating a Let's Encrypt certificate during the firs
[acme]
email = "test@traefik.io"
storage = "acme.json"
-dnsProvider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
-delayDontCheckDNS = 0
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
+ [acme.dnsChallenge]
+ provider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
+ delayBeforeCheck = 0
[[acme.domains]]
main = "local1.com"
@@ -173,12 +193,14 @@ entryPoint = "https"
DNS challenge needs environment variables to be executed.
This variables have to be set on the machine/container which host Traefik.
-These variables are described [in this section](/configuration/acme/#dnsprovider).
+These variables are described [in this section](/configuration/acme/#provider).
-### OnHostRule option and provided certificates
+### OnHostRule option and provided certificates (with HTTP challenge)
```toml
[entryPoints]
+ [entryPoints.http]
+ address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@@ -192,10 +214,11 @@ storage = "acme.json"
onHostRule = true
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
-
+ [acme.httpChallenge]
+ entryPoint = "http"
```
-Traefik will only try to generate a Let's encrypt certificate if the domain cannot be checked by the provided certificates.
+Traefik will only try to generate a Let's encrypt certificate (thanks to `HTTP-01` challenge) if the domain cannot be checked by the provided certificates.
### Cluster mode
@@ -207,6 +230,8 @@ Before you use Let's Encrypt in a Traefik cluster, take a look to [the key-value
```toml
[entryPoints]
+ [entryPoints.http]
+ address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@@ -217,6 +242,9 @@ storage = "traefik/acme/account"
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
+[acme.httpChallenge]
+ entryPoint = "http"
+
[[acme.domains]]
main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"]
@@ -244,10 +272,12 @@ The `consul` provider contains the configuration.
```toml
[frontends]
+
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"
+
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
@@ -255,10 +285,11 @@ The `consul` provider contains the configuration.
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
+
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
- rule = "Path:/test"
+ rule = "Path:/test"
```
## Enable Basic authentication in an entrypoint
@@ -272,6 +303,7 @@ Passwords are encoded in MD5: you can use htpasswd to generate those ones.
```toml
defaultEntryPoints = ["http"]
+
[entryPoints]
[entryPoints.http]
address = ":80"
@@ -286,6 +318,7 @@ via a configurable header value.
```toml
defaultEntryPoints = ["http"]
+
[entryPoints]
[entryPoints.http]
address = ":80"
diff --git a/examples/acme/acme.toml b/examples/acme/acme.toml
index c6f6348ba..151ffb54c 100644
--- a/examples/acme/acme.toml
+++ b/examples/acme/acme.toml
@@ -19,6 +19,8 @@ entryPoint = "https"
onDemand = false
OnHostRule = true
caServer = "http://traefik.localhost.com:4000/directory"
+[acme.httpChallenge]
+entryPoint="http"
[web]
diff --git a/examples/acme/compose-acme.yml b/examples/acme/compose-acme.yml
index f9e9c32d0..c3c6317e3 100644
--- a/examples/acme/compose-acme.yml
+++ b/examples/acme/compose-acme.yml
@@ -78,6 +78,7 @@ services :
- "80:80"
- "443:443"
- "5001:443" # Needed for SNI challenge
+ - "5002:80" # Needed for HTTP challenge
expose:
- "8080"
labels:
diff --git a/examples/cluster/docker-compose.yml b/examples/cluster/docker-compose.yml
index c45102874..0f9640f6b 100644
--- a/examples/cluster/docker-compose.yml
+++ b/examples/cluster/docker-compose.yml
@@ -136,11 +136,13 @@ services:
expose:
- "443"
- "5001"
+ - "5002"
ports:
- "80:80"
- "8080:8080"
- "443:443"
- "5001:443" # Needed for SNI challenge
+ - "5002:80" # Needed for HTTP challenge
networks:
net:
ipv4_address: 10.0.1.8
@@ -157,6 +159,7 @@ services:
expose:
- "443"
- "5001"
+ - "5002"
ports:
- "88:80"
- "8888:8080"
diff --git a/examples/cluster/traefik.toml.tmpl b/examples/cluster/traefik.toml.tmpl
index 2569e66d8..891e994b1 100644
--- a/examples/cluster/traefik.toml.tmpl
+++ b/examples/cluster/traefik.toml.tmpl
@@ -15,6 +15,8 @@ storage = "traefik/acme/account"
entryPoint = "https"
OnHostRule = true
caServer = "http://traefik.boulder.com:4000/directory"
+[acme.httpChallenge]
+entryPoint="http"
[web]
diff --git a/integration/acme_test.go b/integration/acme_test.go
index 52326f7ca..01da7ca94 100644
--- a/integration/acme_test.go
+++ b/integration/acme_test.go
@@ -72,6 +72,26 @@ func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificate(c *check.C) {
s.retrieveAcmeCertificate(c, testCase)
}
+// Test OnDemand option with none provided certificate and challenge HTTP-01
+func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateHTTP01(c *check.C) {
+ testCase := AcmeTestCase{
+ traefikConfFilePath: "fixtures/acme/acme_http01.toml",
+ onDemand: true,
+ domainToCheck: acmeDomain}
+
+ s.retrieveAcmeCertificate(c, testCase)
+}
+
+// Test OnHostRule option with none provided certificate and challenge HTTP-01
+func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificateHTTP01(c *check.C) {
+ testCase := AcmeTestCase{
+ traefikConfFilePath: "fixtures/acme/acme_http01.toml",
+ onDemand: false,
+ domainToCheck: acmeDomain}
+
+ s.retrieveAcmeCertificate(c, testCase)
+}
+
// Test OnDemand option with a wildcard provided certificate
func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateWithWildcard(c *check.C) {
testCase := AcmeTestCase{
diff --git a/integration/fixtures/acme/acme_http01.toml b/integration/fixtures/acme/acme_http01.toml
new file mode 100644
index 000000000..1e4f354fe
--- /dev/null
+++ b/integration/fixtures/acme/acme_http01.toml
@@ -0,0 +1,35 @@
+logLevel = "DEBUG"
+
+defaultEntryPoints = ["http", "https"]
+
+[entryPoints]
+ [entryPoints.http]
+ address = ":5002"
+ [entryPoints.https]
+ address = ":5001"
+ [entryPoints.https.tls]
+
+
+[acme]
+email = "test@traefik.io"
+storage = "/dev/null"
+entryPoint = "https"
+onDemand = {{.OnDemand}}
+OnHostRule = {{.OnHostRule}}
+caServer = "http://{{.BoulderHost}}:4000/directory"
+[acme.httpchallenge]
+entrypoint="http"
+
+[file]
+
+[backends]
+ [backends.backend]
+ [backends.backend.servers.server1]
+ url = "http://127.0.0.1:9010"
+
+
+[frontends]
+ [frontends.frontend]
+ backend = "backend"
+ [frontends.frontend.routes.test]
+ rule = "Host:traefik.acme.wtf"
diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go
index a1367977d..4983d900b 100644
--- a/provider/kubernetes/kubernetes.go
+++ b/provider/kubernetes/kubernetes.go
@@ -80,11 +80,11 @@ func (p *Provider) newK8sClient() (Client, error) {
}
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" {
- log.Infof("Creating in-cluster Provider client%s\n", withEndpoint)
+ log.Infof("Creating in-cluster Provider client%s", withEndpoint)
return NewInClusterClient(p.Endpoint)
}
- log.Infof("Creating cluster-external Provider client%s\n", withEndpoint)
+ log.Infof("Creating cluster-external Provider client%s", withEndpoint)
return NewExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath)
}
diff --git a/server/server.go b/server/server.go
index 4265305ee..79d067865 100644
--- a/server/server.go
+++ b/server/server.go
@@ -758,6 +758,10 @@ func (s *Server) addInternalPublicRoutes(entryPointName string, router *mux.Rout
if s.globalConfiguration.Ping != nil && s.globalConfiguration.Ping.EntryPoint != "" && s.globalConfiguration.Ping.EntryPoint == entryPointName {
s.globalConfiguration.Ping.AddRoutes(router)
}
+
+ if s.globalConfiguration.ACME != nil && s.globalConfiguration.ACME.HTTPChallenge != nil && s.globalConfiguration.ACME.HTTPChallenge.EntryPoint == entryPointName {
+ s.globalConfiguration.ACME.AddRoutes(router)
+ }
}
func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher, middlewares []negroni.Handler, internalMiddlewares []negroni.Handler) (*http.Server, net.Listener, error) {