Merge branch '1.5.0-rc5' into master

This commit is contained in:
Fernandez Ludovic 2018-01-15 17:27:37 +01:00
commit 89d90de7d8
74 changed files with 914 additions and 385 deletions

2
.gitignore vendored
View file

@ -11,4 +11,4 @@
*.log *.log
*.exe *.exe
.DS_Store .DS_Store
/example/acme/acme.json /examples/acme/acme.json

View file

@ -1,5 +1,28 @@
# Change Log # Change Log
## [v1.5.0-rc5](https://github.com/containous/traefik/tree/v1.5.0-rc5) (2018-01-15)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc4...v1.5.0-rc5)
**Enhancements:**
- **[acme]** Add Let's Encrypt HTTP Challenge ([#2701](https://github.com/containous/traefik/pull/2701) by [Juliens](https://github.com/Juliens))
**Bug fixes:**
- **[acme,logs]** Modify DEBUG messages to get ACME certificates ([#2685](https://github.com/containous/traefik/pull/2685) by [nmengin](https://github.com/nmengin))
- **[authentication,middleware]** Fix concurrent map writes on digest auth ([#2695](https://github.com/containous/traefik/pull/2695) by [mmatur](https://github.com/mmatur))
- **[docker]** Typo in Docker template. ([#2692](https://github.com/containous/traefik/pull/2692) by [ldez](https://github.com/ldez))
- **[docker]** Return errors from Docker client.Events ([#2689](https://github.com/containous/traefik/pull/2689) by [BlakeMesdag](https://github.com/BlakeMesdag))
- **[kv]** List entries parsing. ([#2669](https://github.com/containous/traefik/pull/2669) by [ldez](https://github.com/ldez))
- **[metrics]** Fix data races. ([#2287](https://github.com/containous/traefik/pull/2287) by [tcolgate](https://github.com/tcolgate))
- **[middleware]** GzipResponse must implement CloseNotifier if ResponseWriter implement it ([#2657](https://github.com/containous/traefik/pull/2657) by [Juliens](https://github.com/Juliens))
- **[websocket]** Add compression and better error handling ([#2702](https://github.com/containous/traefik/pull/2702) by [Juliens](https://github.com/Juliens))
- Fix: timeout integration test ([#2679](https://github.com/containous/traefik/pull/2679) by [ldez](https://github.com/ldez))
**Documentation:**
- **[cluster]** Add a clustering example with Docker Swarm ([#2589](https://github.com/containous/traefik/pull/2589) by [jmaitrehenry](https://github.com/jmaitrehenry))
- **[k8s]** Apply various contentual and stylish improvements to the k8s docs. ([#2677](https://github.com/containous/traefik/pull/2677) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Document rewrite-target annotation. ([#2676](https://github.com/containous/traefik/pull/2676) by [timoreimann](https://github.com/timoreimann))
- **[provider,webui]** Fix redirect problem on dashboard + docs/tests on [web] ([#2686](https://github.com/containous/traefik/pull/2686) by [Juliens](https://github.com/Juliens))
## [v1.5.0-rc4](https://github.com/containous/traefik/tree/v1.5.0-rc4) (2018-01-04) ## [v1.5.0-rc4](https://github.com/containous/traefik/tree/v1.5.0-rc4) (2018-01-04)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc3...v1.5.0-rc4) [All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc3...v1.5.0-rc4)

11
Gopkg.lock generated
View file

@ -125,10 +125,11 @@
version = "v1.0.0" version = "v1.0.0"
[[projects]] [[projects]]
branch = "containous-fork"
name = "github.com/abbot/go-http-auth" name = "github.com/abbot/go-http-auth"
packages = ["."] packages = ["."]
revision = "0ddd408d5d60ea76e320503cc7dd091992dee608" revision = "65b0cdae8d7fe5c05c7430e055938ef6d24a66c9"
version = "v0.4.0" source = "github.com/containous/go-http-auth"
[[projects]] [[projects]]
name = "github.com/aokoli/goutils" name = "github.com/aokoli/goutils"
@ -418,7 +419,7 @@
name = "github.com/docker/leadership" name = "github.com/docker/leadership"
packages = ["."] packages = ["."]
revision = "af20da7d3e62be9259835e93261acf931b5adecf" revision = "af20da7d3e62be9259835e93261acf931b5adecf"
source = "https://github.com/containous/leadership.git" source = "github.com/containous/leadership"
[[projects]] [[projects]]
name = "github.com/docker/libcompose" name = "github.com/docker/libcompose"
@ -552,7 +553,7 @@
name = "github.com/go-check/check" name = "github.com/go-check/check"
packages = ["."] packages = ["."]
revision = "ca0bf163426aa183d03fd4949101785c0347f273" revision = "ca0bf163426aa183d03fd4949101785c0347f273"
source = "https://github.com/containous/check.git" source = "github.com/containous/check"
[[projects]] [[projects]]
name = "github.com/go-ini/ini" name = "github.com/go-ini/ini"
@ -1481,6 +1482,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "0f85e152f938507b05ee27ce99b5d2ffe7999d4c27123277a1c398deba0c08b1" inputs-digest = "aa1aa76d15a49bbeee747782a71809fcfbaccf4586e924e70a6d93c51c173a30"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View file

@ -38,8 +38,9 @@ ignored = ["github.com/sirupsen/logrus"]
name = "github.com/NYTimes/gziphandler" name = "github.com/NYTimes/gziphandler"
[[constraint]] [[constraint]]
branch = "containous-fork"
name = "github.com/abbot/go-http-auth" name = "github.com/abbot/go-http-auth"
version = "0.4.0" source = "github.com/containous/go-http-auth"
[[constraint]] [[constraint]]
branch = "master" branch = "master"
@ -74,8 +75,9 @@ ignored = ["github.com/sirupsen/logrus"]
version = "14.0.0" version = "14.0.0"
[[constraint]] [[constraint]]
branch = "master"
name = "github.com/docker/leadership" name = "github.com/docker/leadership"
source = "https://github.com/containous/leadership.git" source = "github.com/containous/leadership"
[[constraint]] [[constraint]]
name = "github.com/docker/libkv" name = "github.com/docker/libkv"
@ -92,7 +94,7 @@ ignored = ["github.com/sirupsen/logrus"]
[[constraint]] [[constraint]]
branch = "fork-containous" branch = "fork-containous"
name = "github.com/go-check/check" name = "github.com/go-check/check"
source = "https://github.com/containous/check.git" source = "github.com/containous/check"
[[constraint]] [[constraint]]
name = "github.com/go-kit/kit" name = "github.com/go-kit/kit"
@ -188,15 +190,15 @@ ignored = ["github.com/sirupsen/logrus"]
revision = "6018b68f96b839edfbe3fb48668853f5dbad88a3" revision = "6018b68f96b839edfbe3fb48668853f5dbad88a3"
source = "github.com/ijc25/Gotty" source = "github.com/ijc25/Gotty"
[[override]]
name = "github.com/gorilla/websocket"
revision = "a69d9f6de432e2c6b296a947d8a5ee88f68522cf"
[[override]] [[override]]
# ALWAYS keep this override # ALWAYS keep this override
name = "github.com/mailgun/timetools" name = "github.com/mailgun/timetools"
revision = "7e6055773c5137efbeb3bd2410d705fe10ab6bfd" revision = "7e6055773c5137efbeb3bd2410d705fe10ab6bfd"
[[override]]
name = "github.com/gorilla/websocket"
revision = "a69d9f6de432e2c6b296a947d8a5ee88f68522cf"
# Must be remove when logrus migration happen # Must be remove when logrus migration happen
[[override]] [[override]]
name = "github.com/vulcand/predicate" name = "github.com/vulcand/predicate"

View file

@ -24,6 +24,7 @@ type Account struct {
PrivateKey []byte PrivateKey []byte
DomainsCertificate DomainsCertificates DomainsCertificate DomainsCertificates
ChallengeCerts map[string]*ChallengeCert ChallengeCerts map[string]*ChallengeCert
HTTPChallenge map[string]map[string][]byte
} }
// ChallengeCert stores a challenge certificate // ChallengeCert stores a challenge certificate

View file

@ -7,6 +7,8 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
fmtlog "log" fmtlog "log"
"net"
"net/http"
"os" "os"
"regexp" "regexp"
"strings" "strings"
@ -14,6 +16,8 @@ import (
"github.com/BurntSushi/ty/fun" "github.com/BurntSushi/ty/fun"
"github.com/cenk/backoff" "github.com/cenk/backoff"
"github.com/containous/flaeg"
"github.com/containous/mux"
"github.com/containous/staert" "github.com/containous/staert"
"github.com/containous/traefik/cluster" "github.com/containous/traefik/cluster"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
@ -37,23 +41,37 @@ type ACME struct {
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'"` 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."` Storage string `description:"File or key used for certificates storage."`
StorageFile string // deprecated 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."` 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."` OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
CAServer string `description:"CA server to use."` CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."` EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
DNSProvider string `description:"Use a DNS based challenge provider rather than HTTPS."` DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge"`
DelayDontCheckDNS int `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` 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."` ACMELogging bool `description:"Enable debug logging of ACME actions."`
client *acme.Client client *acme.Client
defaultCertificate *tls.Certificate defaultCertificate *tls.Certificate
store cluster.Store store cluster.Store
challengeProvider *challengeProvider challengeTLSProvider *challengeTLSProvider
challengeHTTPProvider *challengeHTTPProvider
checkOnDemandDomain func(domain string) bool checkOnDemandDomain func(domain string) bool
jobs *channels.InfiniteChannel jobs *channels.InfiniteChannel
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"` TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
dynamicCerts *safe.Safe 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 //Domains parse []Domain
type Domains []Domain type Domains []Domain
@ -107,15 +125,39 @@ func (a *ACME) init() error {
return err return err
} }
a.defaultCertificate = cert a.defaultCertificate = cert
// TODO: to remove in the future
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() a.jobs = channels.NewInfiniteChannel()
return nil 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 // 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 { func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, certs *safe.Safe, checkOnDemandDomain func(domain string) bool) error {
err := a.init() err := a.init()
@ -155,7 +197,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
} }
a.store = datastore a.store = datastore
a.challengeProvider = &challengeProvider{store: a.store} a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
ticker := time.NewTicker(24 * time.Hour) ticker := time.NewTicker(24 * time.Hour)
leadership.Pool.AddGoCtx(func(ctx context.Context) { leadership.Pool.AddGoCtx(func(ctx context.Context) {
@ -171,7 +213,11 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
} }
}) })
leadership.AddListener(func(elected bool) error { leadership.AddListener(a.leadershipListener)
return nil
}
func (a *ACME) leadershipListener(elected bool) error {
if elected { if elected {
_, err := a.store.Load() _, err := a.store.Load()
if err != nil { if err != nil {
@ -191,9 +237,6 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
} }
needRegister = true needRegister = true
} }
if err != nil {
return err
}
a.client, err = a.buildACMEClient(account) a.client, err = a.buildACMEClient(account)
if err != nil { if err != nil {
return err return err
@ -212,6 +255,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
log.Debug("AgreeToTOS...") log.Debug("AgreeToTOS...")
err = a.client.AgreeToTOS() err = a.client.AgreeToTOS()
if err != nil { if err != nil {
log.Debug(err)
// Let's Encrypt Subscriber Agreement renew ? // Let's Encrypt Subscriber Agreement renew ?
reg, err := a.client.QueryRegistration() reg, err := a.client.QueryRegistration()
if err != nil { if err != nil {
@ -233,8 +277,6 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
a.runJobs() a.runJobs()
} }
return nil return nil
})
return nil
} }
// CreateLocalConfig creates a tls.config using local ACME configuration // CreateLocalConfig creates a tls.config using local ACME configuration
@ -253,7 +295,7 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, certs *safe.Safe, checkO
a.TLSConfig = tlsConfig a.TLSConfig = tlsConfig
localStore := NewLocalStore(a.Storage) localStore := NewLocalStore(a.Storage)
a.store = localStore a.store = localStore
a.challengeProvider = &challengeProvider{store: a.store} a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
var needRegister bool var needRegister bool
var account *Account var account *Account
@ -337,7 +379,7 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
return providedCertificate, nil 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) log.Debugf("ACME got challenge %s", domain)
return challengeCert, nil return challengeCert, nil
} }
@ -351,7 +393,7 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
} }
return a.loadCertificateOnDemand(clientHello) return a.loadCertificateOnDemand(clientHello)
} }
log.Debugf("ACME got nothing %s", domain) log.Debugf("No certificate found or generated for %s", domain)
return nil, nil return nil, nil
} }
@ -472,16 +514,16 @@ func (a *ACME) storeRenewedCertificate(certificateResource *DomainsCertificate,
return nil return nil
} }
func dnsOverrideDelay(delay int) error { func dnsOverrideDelay(delay flaeg.Duration) error {
var err error var err error
if delay > 0 { 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) { acme.PreCheckDNS = func(_, _ string) (bool, error) {
time.Sleep(time.Duration(delay) * time.Second) time.Sleep(time.Duration(delay))
return true, nil return true, nil
} }
} else if delay < 0 { } else if delay < 0 {
err = fmt.Errorf("invalid negative DelayDontCheckDNS: %d", delay) err = fmt.Errorf("invalid negative DelayBeforeCheck: %d", delay)
} }
return err return err
} }
@ -497,25 +539,29 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
return nil, err return nil, err
} }
if len(a.DNSProvider) > 0 { if a.DNSChallenge != nil && len(a.DNSChallenge.Provider) > 0 {
log.Debugf("Using DNS Challenge provider: %s", a.DNSProvider) log.Debugf("Using DNS Challenge provider: %s", a.DNSChallenge.Provider)
err = dnsOverrideDelay(a.DelayDontCheckDNS) err = dnsOverrideDelay(a.DNSChallenge.DelayBeforeCheck)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var provider acme.ChallengeProvider var provider acme.ChallengeProvider
provider, err = dns.NewDNSChallengeProviderByName(a.DNSProvider) provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider)
if err != nil { if err != nil {
return nil, err return nil, err
} }
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01}) client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
err = client.SetChallengeProvider(acme.DNS01, provider) 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 { } else {
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01}) 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 { if err != nil {
@ -623,7 +669,7 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
// Get provided certificate which check a domains list (Main and SANs) // Get provided certificate which check a domains list (Main and SANs)
// from static and dynamic provided certificates // from static and dynamic provided certificates
func (a *ACME) getProvidedCertificate(domains []string) *tls.Certificate { func (a *ACME) getProvidedCertificate(domains []string) *tls.Certificate {
log.Debugf("Look for provided certificate to validate %s...", domains) log.Debugf("Looking for provided certificate to validate %s...", domains)
cert := searchProvidedCertificateForDomains(domains, a.TLSConfig.NameToCertificate) cert := searchProvidedCertificateForDomains(domains, a.TLSConfig.NameToCertificate)
if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil { if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil {
cert = searchProvidedCertificateForDomains(domains, a.dynamicCerts.Get().(*traefikTls.DomainsCertificates).Get().(map[string]*tls.Certificate)) cert = searchProvidedCertificateForDomains(domains, a.dynamicCerts.Get().(*traefikTls.DomainsCertificates).Get().(map[string]*tls.Certificate))
@ -659,7 +705,7 @@ func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple) certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
if len(failures) > 0 { if len(failures) > 0 {
log.Error(failures) 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) log.Debugf("Loaded ACME certificates %s", domains)
return &Certificate{ return &Certificate{

43
acme/acme_example.json Normal file
View file

@ -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": "MIIJJwIBAAKCAgEAy5a71suIqvEtovDmDVQ3SSNagk5IVCFI/TvqWpEXSrdbcDE2C+PTEtEUJuLkYwygcpiWYbPmXgdS628vQCw5Uo4DeDyHiuysJOWBLaWow3p9goOdhnPbGBq0liIR9xXyRoctdipVk8UiO9scWsu4jMBM3sMr7/yBWPfYYiLEQmZGFO3iE7Oqr55h/kncHIj5lUQY1j/jkftqxlxUB5/0quyJ7l915j5QY++eY7h4GEhRvx0TlUpi+CnRtRblGeDDDilXZD6bQN2962WdKecsmRaYx+ttLz6jCPXz2VDJRWNcIS501ne2Zh3hzw/DS6IRd2GIia1Wg4sisi9epC9sumXPHi6xzR6+/i/nsFjdtTkUcV8HmorOYoc820KQVZaLScxa8e7+ixpOd6mr6AIbEf7dBAkb9f/iK3GwpqKD8yNcaj1EQgNSyJSjnKSulXI/GwkGnuXe00Qpb1a8ha5Z8yWg7XmZZnJyAZrmK60RfwRNQ1rO5ioerNUBJ2KYTYNzVjBdob9Ug6Cjh4bEKNNjqcbjQ50/Z97Vw40xzpDQ/fYllc6n92eSuv6olxFJTmK7EhHuanDzITngaqei3zL9RwQ7P+1jfEZ03qmGrQYYqXcsS46PQ8cE+frzY2mKp16pRNCG7+03gKVGV0JHyW1aYbevNUk7OumCAXhC2YOigBkCAwEAAQKCAgA8XW1EuwTC6tAFSDhuK1JZNUpY6K05hMUHkQRj5jFpzgQmt/C2hc7H/YZkIVJmrA/G6sdsINNlffZwKH9yH6q/d6w/snLeFl7UcdhjmIL5sxAT6sKCY0fLVd/FxERfZvp3Pw2Tw+mr7v+/j7BQm6cU1M/2HRiiB9SydIqMTpKyvXB6NC6ceOFbQTL9GxlQvKyEPbS/kiH/3vRB7I5d1GfPZmNfcp6ark9X0my8VK4HRSo36H8t/OhrfLrZXvh/O82aHVf0OTv/d8AgU/jNu+XVXoXegUfWglQFDChJf1KuaE+g5w1tqgFDNgkGRD475soXA6xgZi0Iw/B9tN3zALzT4IiAW1q72feeTgKOMA2zGtKXxQZZSOV+DuWFZNz/tT7XqGQThqxM09CHv2WGOe80vobtegXYTUt90hysrqIZmBW5XYdzQlJh1KWTtfCaTrWd47kbGvhkEPc8aA3Ji4/AqfkVXiqwaLu+MSlgzPpRj7U7UAIDqnpZjgttgLp74Ujnk3bTaUzdyyNqYDBG3IFGr/Sv+2GQDAUn/PYRJKWr0BteqOzX9zvW3zY8g9CYVXfK/AW3RMWLV8ly6vH/gWqa9gEuzRNRlzjUU6/HCVbUx3UT8RMWH2TQ0uuQZr5JX1iTwjeeT0dEIly1NnRQC92wcrE4UUTBEF3krGVpDBf0AQKCAQEA4jB8w+2fwzbF8X+gCODcY7sTeJRunzGy+jbdaLkcThuylga+6W3ZgWx0BD30ql9K2mouCVu86fCTnBeXXEC3QoTdgw/EzJ83+4JU3QSDdzs9Ta9vLHyvrpUkQfZ8UZpeLLmFsmsBMbBbnfw0S1TzXDsgrAc+G4tia8nO/Iqu75kEMGzmHQAvmN3iSqc1aTS4qumbB19g+v+csq9NEht4F9jt39KotG+OD3MxCxtMu7vxAkJRjFFcgcbb2Rtqe/kQEKA1vLEAJg27lV4k8XibCSerVUR6IzT8WZHrNiXmpRguTLl2k8uFUdCOOx6aLGyRVJ6+8SgIsMR540vnxwQzEQKCAQEA5mu2wtWT19mvXopC3easPsXIPzc5oaRkqfWZYT1KHcVQ7NIXsE3vCjcf/3igZ8l/FVQ4G4fpk/GoTqlpV5Aq/JHCpVOR2O69uB+W4kWgliejpHvF9gszzAYnC8lIXqDbWiinBhmm3ii8sDGAoBaSDw5NMUq3mI+nd8zZ+jx1bLBczDafmQ0YKr8k0YaROxIgoBgDOQDdSqG387lwzpza2DKI5Al3HfS42zjT0RmBahPiuT2aEoUZmIYuvFY0fEjfkpbdvLyexHfZCILRUGlG1nAwASFg86lp+mFSBJ3E3cvbP0CpbFGxon5u4Ao3/7htoOh6huh7MQ91h41fv1hsiQKCAQAe7WRR4e7jYVzlbX7zV9Oqq0y5QwpxJ/mB7viNNiphn7Xmf5uhDU0dPjgK0HHgzdDNVpFe5DVLg4KbaDpg+dRU+xfSsNhG5kpgUGzMH67eIbJ7Kc64tX/MDkZ74nkTK1lPIjrer3TlV2jfjDmWR1JTPR51hzP9ziwx8tEjhM7woeqJuIoqUvkvHL+xV3WdIgFSFUkGVAtNpp/FauTN4gWktRupbAN3UH2LLUP6ccwnK0aD+Y9u8T0F3av33qDLvL1umIlgeI89pMkOXmYMwmHoeY0axpcwszECCkqwB7SmxEyoXv+Qq9ZZ3ntkKAYKpvmkKWSQUtoFWYgVBS727mMRAoIBABLdwusU/bPwuPEutObiWjwRiaHTbb6UbUGVQGe70vO5EjUxxorC9s2JUe9i+w9EakleyfFHIZLheHxoVp26yio/7QYIX6q5cYM/4uTH+qwQts9i6wSISkdsQYovguNsnEk3huVy+Dy8bSaoBvYUowTkkOF2Uq4FJRskBLz+ckbh8dcuqcaoUdA+Mk+NixqhE1bIYIssTPItZ5hnGJtyMGD/UkIJnF0ximk4r+8w/W2oDypHpvPZPg1E/1KgZE/Az7166NDpSL6haX3O6ECDPi+Uo/mTuBJ7TpgXm9WQ7WuTo3H8Y2LhFYBOhdmGPKuNeDxyjIW7R0rvDxp4MtzB6rECggEAJIl7/qp1lxUQPQJRTsEYBkOtdRw0IGG1Rcj0emhHaBN05c9opCy+Osb7mVeU5ZiULe5kD02phL+36pEumprz7QzN46Y5pZc8AQ2W/QkeL4Wo9U9QzczvQQzc1EqrBkzvQTZtBhn4DRzz0IuTn1beVyHtBZeNpBFgMQFv9VYQuUNwFoTOkkQrBRnYbXH6KEnhF3c/1Hzi4KHVdHdfZ3LH7KFQJ34xio0q2tWQSQYeybmwOXdd9sxpz/Y4KBS9fqm7UrwnPK8yuOc05HLEaws+1iam5YyJprlQo3mGKe0wRztwn44HDeQr70LlFm0lzigVAv0hSiWO1Q5hJL7nDu8m/Q==",
"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": {}
}

View file

@ -267,7 +267,7 @@ cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`)
}`)) }`))
})) }))
defer ts.Close() 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) client, err := a.buildACMEClient(account)
if err != nil { if err != nil {

View file

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

View file

@ -23,15 +23,15 @@ import (
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
) )
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil) var _ acme.ChallengeProviderTimeout = (*challengeTLSProvider)(nil)
type challengeProvider struct { type challengeTLSProvider struct {
store cluster.Store store cluster.Store
lock sync.RWMutex 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("Challenge GetCertificate %s", domain) log.Debugf("Looking for an existing ACME challenge for %s...", domain)
if !strings.HasSuffix(domain, ".acme.invalid") { if !strings.HasSuffix(domain, ".acme.invalid") {
return nil, false return nil, false
} }
@ -67,7 +67,7 @@ func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate
return result, true 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) log.Debugf("Challenge Present %s", domain)
cert, _, err := tlsSNI01ChallengeCert(keyAuth) cert, _, err := tlsSNI01ChallengeCert(keyAuth)
if err != nil { if err != nil {
@ -88,7 +88,7 @@ func (c *challengeProvider) Present(domain, token, keyAuth string) error {
return transaction.Commit(account) 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) log.Debugf("Challenge CleanUp %s", domain)
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
@ -101,7 +101,7 @@ func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
return transaction.Commit(account) 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 return 60 * time.Second, 5 * time.Second
} }

41
acme/localStore_test.go Normal file
View file

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

View file

@ -15,7 +15,7 @@ type DashboardHandler struct{}
func (g DashboardHandler) AddRoutes(router *mux.Router) { func (g DashboardHandler) AddRoutes(router *mux.Router) {
// Expose dashboard // Expose dashboard
router.Methods(http.MethodGet).Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) { router.Methods(http.MethodGet).Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
http.Redirect(response, request, "/dashboard/", 302) http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302)
}) })
router.Methods(http.MethodGet).PathPrefix("/dashboard/"). router.Methods(http.MethodGet).PathPrefix("/dashboard/").
Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"}))) Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"})))

View file

@ -20,8 +20,8 @@ type Handler struct {
Debug bool `export:"true"` Debug bool `export:"true"`
CurrentConfigurations *safe.Safe CurrentConfigurations *safe.Safe
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"` Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
Stats *thoas_stats.Stats Stats *thoas_stats.Stats `json:"-"`
StatsRecorder *middlewares.StatsRecorder StatsRecorder *middlewares.StatsRecorder `json:"-"`
} }
var ( var (

View file

@ -169,7 +169,7 @@ func TestDo_globalConfiguration(t *testing.T) {
OnHostRule: true, OnHostRule: true,
CAServer: "CAServer", CAServer: "CAServer",
EntryPoint: "EntryPoint", EntryPoint: "EntryPoint",
DNSProvider: "DNSProvider", DNSChallenge: &acme.DNSChallenge{Provider: "DNSProvider"},
DelayDontCheckDNS: 666, DelayDontCheckDNS: 666,
ACMELogging: true, ACMELogging: true,
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{

View file

@ -243,6 +243,23 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
log.Errorln("Error using file configuration backend, no filename defined") 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 // DefaultEntryPoints holds default entry points

View file

@ -632,8 +632,7 @@ Once a day (the first call begins 10 minutes after the start of Træfik), we col
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
[web] [api]
address = ":8080"
[Docker] [Docker]
endpoint = "tcp://10.10.10.10:2375" endpoint = "tcp://10.10.10.10:2375"
@ -663,8 +662,7 @@ Once a day (the first call begins 10 minutes after the start of Træfik), we col
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
[web] [api]
address = ":8080"
[Docker] [Docker]
Endpoint = "xxxx" Endpoint = "xxxx"

View file

@ -7,10 +7,14 @@ See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) an
```toml ```toml
# Sample entrypoint configuration when using ACME. # Sample entrypoint configuration when using ACME.
[entryPoints] [entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https] [entryPoints.https]
address = ":443" address = ":443"
[entryPoints.https.tls] [entryPoints.https.tls]
```
```toml
# Enable ACME (Let's Encrypt): automatic SSL. # Enable ACME (Let's Encrypt): automatic SSL.
[acme] [acme]
@ -33,17 +37,16 @@ email = "test@traefik.io"
storage = "acme.json" storage = "acme.json"
# or `storage = "traefik/acme/account"` if using KV store. # or `storage = "traefik/acme/account"` if using KV store.
# Entrypoint to proxy acme challenge/apply certificates to. # Entrypoint to proxy acme apply certificates to.
# WARNING, must point to an entrypoint on port 443 # WARNING, if the TLS-SNI-01 challenge is used, it must point to an entrypoint on port 443
# #
# Required # Required
# #
entryPoint = "https" 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 (Deprecated, replaced by [acme.dnsChallenge])
# Optional
# #
# dnsProvider = "digitalocean" # dnsProvider = "digitalocean"
@ -51,25 +54,29 @@ entryPoint = "https"
# If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds. # If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds.
# Useful if internal networks block external DNS queries. # Useful if internal networks block external DNS queries.
# #
# Optional # Optional (Deprecated, replaced by [acme.dnsChallenge])
# Default: 0
# #
# delayDontCheckDNS = 0 # delayDontCheckDNS = 0
# If true, display debug log messages from the acme client library. # If true, display debug log messages from the acme client library.
# #
# Optional # Optional
# Default: false
# #
# acmeLogging = true # acmeLogging = true
# Enable on demand certificate. (Deprecated) # Enable on demand certificate generation.
# #
# Optional # Optional (Deprecated)
# Default: false
# #
# onDemand = true # onDemand = true
# Enable certificate generation on frontends Host rules. # Enable certificate generation on frontends Host rules.
# #
# Optional # Optional
# Default: false
# #
# onHostRule = true # onHostRule = true
@ -78,6 +85,7 @@ entryPoint = "https"
# - Leave comment to go to prod. # - Leave comment to go to prod.
# #
# Optional # Optional
# Default: "https://acme-v01.api.letsencrypt.org/directory"
# #
# caServer = "https://acme-staging.api.letsencrypt.org/directory" # caServer = "https://acme-staging.api.letsencrypt.org/directory"
@ -93,11 +101,48 @@ entryPoint = "https"
# main = "local3.com" # main = "local3.com"
# [[acme.domains]] # [[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 !!! note
ACME entryPoint has to be relied to the port 443, otherwise ACME Challenges can not be done. If `TLS-SNI-01` challenge is used, `acme.entryPoint` has to be reachable by Let's Encrypt through the port 443.
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 `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` ### `storage`
@ -110,7 +155,7 @@ storage = "acme.json"
File or key used for certificates storage. 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: - create a file on your host and mount it as a volume:
```toml ```toml
@ -133,19 +178,60 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
!!! note !!! 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`. 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 ```toml
[acme] [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: Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables to enable setting it:
@ -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. | | [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` | | [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` |
### `delayDontCheckDNS` #### `delayBeforeCheck`
```toml By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
[acme] If `delayBeforeCheck` is greater than zero, avoid this & instead just wait so many seconds.
# ...
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.
Useful if internal networks block external DNS queries. Useful if internal networks block external DNS queries.
!!! note
This field has no sense if a `provider` is not defined.
### `onDemand` (Deprecated) ### `onDemand` (Deprecated)
!!! warning
This option is deprecated.
```toml ```toml
[acme] [acme]
# ... # ...
@ -208,9 +293,6 @@ This will request a certificate from Let's Encrypt during the first TLS handshak
!!! warning !!! warning
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits). Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
!!! warning
This option is deprecated.
### `onHostRule` ### `onHostRule`
```toml ```toml
@ -240,21 +322,21 @@ CA server to use.
- Uncomment the line to run on the staging Let's Encrypt server. - Uncomment the line to run on the staging Let's Encrypt server.
- Leave comment to go to prod. - Leave comment to go to prod.
### `domains` ### `acme.domains`
```toml ```toml
[acme] [acme]
# ... # ...
[[acme.domains]] [[acme.domains]]
main = "local1.com" main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"] sans = ["test1.local1.com", "test2.local1.com"]
[[acme.domains]] [[acme.domains]]
main = "local2.com" main = "local2.com"
sans = ["test1.local2.com", "test2.local2.com"] sans = ["test1.local2.com", "test2.local2.com"]
[[acme.domains]] [[acme.domains]]
main = "local3.com" main = "local3.com"
[[acme.domains]] [[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). Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
Each domain & SANs will lead to a certificate request. 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)

View file

@ -161,7 +161,7 @@ curl -s "http://localhost:8080/health" | jq .
// average response time in seconds // average response time in seconds
"average_response_time_sec": 0.8648016000000001, "average_response_time_sec": 0.8648016000000001,
// request statistics [requires --web.statistics to be set] // request statistics [requires --statistics to be set]
// ten most recent requests with 4xx and 5xx status codes // ten most recent requests with 4xx and 5xx status codes
"recent_errors": [ "recent_errors": [
{ {

View file

@ -4,7 +4,6 @@ Træfik can be configured to use Kubernetes Ingress as a backend configuration.
See also [Kubernetes user guide](/user-guide/kubernetes). See also [Kubernetes user guide](/user-guide/kubernetes).
## Configuration ## Configuration
```toml ```toml
@ -44,7 +43,7 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
# #
# namespaces = ["default", "production"] # namespaces = ["default", "production"]
# Ingress label selector to identify Ingress objects that should be processed. # Ingress label selector to filter Ingress objects that should be processed.
# #
# Optional # Optional
# Default: empty (process all Ingresses) # Default: empty (process all Ingresses)
@ -75,28 +74,31 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
### `endpoint` ### `endpoint`
The Kubernetes server endpoint. The Kubernetes server endpoint as URL.
When deployed as a replication controller in Kubernetes, Traefik will use the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` to construct the endpoint. When deployed into Kubernetes, Traefik will read the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` to construct the endpoint.
Secure token will be found in `/var/run/secrets/kubernetes.io/serviceaccount/token` and SSL CA cert in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt` The access token will be looked up in `/var/run/secrets/kubernetes.io/serviceaccount/token` and the SSL CA certificate in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`.
Both are provided mounted automatically when deployed inside Kubernetes.
The endpoint may be given to override the environment variable values. The endpoint may be specified to override the environment variable values inside a cluster.
When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client. When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client.
In this case, the endpoint is required. In this case, the endpoint is required.
Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster from localhost. Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster using the granted autentication and authorization of the associated kubeconfig.
### `labelselector` ### `labelselector`
Ingress label selector to identify Ingress objects that should be processed. By default, Traefik processes all Ingress objects in the configured namespaces.
A label selector can be defined to filter on specific Ingress objects only.
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details.
## Annotations ## Annotations
Annotations can be used on containers to override default behaviour for the whole Ingress resource: ### General annotations
The following general annotations are applicable on the Ingress object:
- `traefik.frontend.rule.type: PathPrefixStrip` - `traefik.frontend.rule.type: PathPrefixStrip`
Override the default frontend rule type. Default: `PathPrefix`. Override the default frontend rule type. Default: `PathPrefix`.
@ -114,44 +116,35 @@ Annotations can be used on containers to override default behaviour for the whol
Override the default frontend PassTLSCert value. Default: `false`. Override the default frontend PassTLSCert value. Default: `false`.
- `ingress.kubernetes.io/rewrite-target: /users` - `ingress.kubernetes.io/rewrite-target: /users`
Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header.
- `ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"`
A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted.
!!! note !!! note
Please note that `traefik.frontend.redirect.regex` and `traefik.frontend.redirect.replacement` do not have to be set if `traefik.frontend.redirect.entryPoint` is defined for the redirection (they will not be used in this case). Please note that `traefik.frontend.redirect.regex` and `traefik.frontend.redirect.replacement` do not have to be set if `traefik.frontend.redirect.entryPoint` is defined for the redirection (they will not be used in this case).
The following annotations are applicable on the Service object associated with a particular Ingress object:
Annotations can be used on the Kubernetes service to override default behaviour:
- `traefik.backend.loadbalancer.method=drr` - `traefik.backend.loadbalancer.method=drr`
Override the default `wrr` load balancer algorithm Override the default `wrr` load balancer algorithm.
- `traefik.backend.loadbalancer.stickiness=true` - `traefik.backend.loadbalancer.stickiness=true`
Enable backend sticky sessions Enable backend sticky sessions.
- `traefik.backend.loadbalancer.stickiness.cookieName=NAME` - `traefik.backend.loadbalancer.stickiness.cookieName=NAME`
Manually set the cookie name for sticky sessions Manually set the cookie name for sticky sessions.
- `traefik.backend.loadbalancer.sticky=true` - `traefik.backend.loadbalancer.sticky=true`
Enable backend sticky sessions (DEPRECATED) Enable backend sticky sessions (DEPRECATED).
Additionally, an annotation can be used on Kubernetes services to set the [circuit breaker expression](/basics/#backends) for a backend.
- `traefik.backend.circuitbreaker: <expression>` - `traefik.backend.circuitbreaker: <expression>`
Set the circuit breaker expression for the backend. Default: `nil`. Set the circuit breaker expression for the backend.
As known from nginx when used as Kubernetes Ingress Controller, a list of IP-Ranges which are allowed to access can be configured by using an ingress annotation: ### Security annotations
- `ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` The following security annotations are applicable on the Ingress object:
An unset or empty list allows all Source-IPs to access.
If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access.
#### Security annotations
The following security annotations can be applied to the ingress object to add security features:
| Annotation | Description | | Annotation | Description |
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ingress.kubernetes.io/allowed-hosts:EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` | | `ingress.kubernetes.io/allowed-hosts:EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` |
| `ingress.kubernetes.io/custom-request-headers:EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> | | `ingress.kubernetes.io/custom-request-headers:EXPR` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `ingress.kubernetes.io/custom-response-headers:EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> | | `ingress.kubernetes.io/custom-response-headers:EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `ingress.kubernetes.io/proxy-headers:EXPR ` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` | | `ingress.kubernetes.io/proxy-headers:EXPR` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` |
| `ingress.kubernetes.io/ssl-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. | | `ingress.kubernetes.io/ssl-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
| `ingress.kubernetes.io/ssl-temporary-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. | | `ingress.kubernetes.io/ssl-temporary-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `ingress.kubernetes.io/ssl-host:HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. | | `ingress.kubernetes.io/ssl-host:HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
@ -171,17 +164,17 @@ The following security annotations can be applied to the ingress object to add s
### Authentication ### Authentication
Is possible to add additional authentication annotations in the Ingress rule. Is possible to add additional authentication annotations to the Ingress object.
The source of the authentication is a secret that contains usernames and passwords inside the key auth. The source of the authentication is a Secret object that contains the credentials.
- `ingress.kubernetes.io/auth-type`: `basic` - `ingress.kubernetes.io/auth-type`: `basic`
Contains the authentication type. The only permitted type is `basic`.
- `ingress.kubernetes.io/auth-secret`: `mysecret` - `ingress.kubernetes.io/auth-secret`: `mysecret`
Contains the usernames and passwords with access to the paths defined in the Ingress Rule. Contains the username and password with access to the paths defined in the Ingress object.
The secret must be created in the same namespace as the Ingress rule. The secret must be created in the same namespace as the Ingress object.
Limitations: The following limitations hold:
- Basic authentication only. - The realm is not configurable; the only supported (and default) value is `traefik`.
- Realm not configurable; only `traefik` default. - The Secret must contain a single file only.
- Secret must contain only single file.

View file

@ -35,6 +35,15 @@ address = ":8080"
# Default: false # Default: false
# #
readOnly = true readOnly = true
# Set the root path for webui and API
#
# Deprecated
# Optional
#
# path = "/mypath"
#
``` ```
## Web UI ## Web UI
@ -80,7 +89,7 @@ Users can be specified directly in the toml file, or indirectly by referencing a
# To enable digest auth on the webui with 2 user/realm/pass: test:traefik:test and test2:traefik:test2 # To enable digest auth on the webui with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
[web.auth.digest] [web.auth.digest]
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"] users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
usersFile = "/path/to/.htdigest" usersFile = "/path/to/.htdigest"
# ... # ...
@ -375,3 +384,35 @@ curl -s "http://localhost:8080/api" | jq .
} }
} }
``` ```
## Path
As web is deprecated, you can handle the `Path` option like this
```toml
[entrypoints.http]
address=":80"
[entrypoints.dashboard]
address=":8080"
[entrypoints.api]
address=":8081"
#Activate API and Dashboard
[api]
entrypoint="api"
[file]
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "http://127.0.0.1:8081"
[frontends]
[frontends.frontend1]
entrypoints=["dashboard"]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "PathPrefixStrip:/yourprefix;PathPrefix:/yourprefix"
```

View file

@ -135,8 +135,8 @@ Users can be specified directly in the toml file, or indirectly by referencing a
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
[entryPoints.http.auth.basic] [entryPoints.http.auth.digest]
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"] users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
usersFile = "/path/to/.htdigest" usersFile = "/path/to/.htdigest"
``` ```

View file

@ -108,7 +108,7 @@ version: '2'
services: services:
proxy: proxy:
image: traefik image: traefik
command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG command: --api --docker --docker.domain=docker.localhost --logLevel=DEBUG
networks: networks:
- webgateway - webgateway
ports: ports:

View file

@ -56,15 +56,18 @@ $ traefik \
--acme \ --acme \
--acme.storage=/etc/traefik/acme/acme.json \ --acme.storage=/etc/traefik/acme/acme.json \
--acme.entryPoint=https \ --acme.entryPoint=https \
--acme.httpChallenge.entryPoint=http \
--acme.email=contact@mydomain.ca --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. 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`. 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. For your email and the entry point, it's `--acme.entryPoint` and `--acme.email` flags.
### Docker configuration ### Docker configuration
@ -90,13 +93,14 @@ services:
traefik: traefik:
image: traefik:1.5 image: traefik:1.5
command: command:
- "--web" - "--api"
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https" - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
- "--entrypoints=Name:https Address::443 TLS" - "--entrypoints=Name:https Address::443 TLS"
- "--defaultentrypoints=http,https" - "--defaultentrypoints=http,https"
- "--acme" - "--acme"
- "--acme.storage=/etc/traefik/acme/acme.json" - "--acme.storage=/etc/traefik/acme/acme.json"
- "--acme.entryPoint=https" - "--acme.entryPoint=https"
- "--acme.httpChallenge.entryPoint=http"
- "--acme.OnHostRule=true" - "--acme.OnHostRule=true"
- "--acme.onDemand=false" - "--acme.onDemand=false"
- "--acme.email=contact@mydomain.ca" - "--acme.email=contact@mydomain.ca"
@ -155,7 +159,7 @@ The initializer in a docker-compose file will be:
image: traefik:1.5 image: traefik:1.5
command: command:
- "storeconfig" - "storeconfig"
- "--web" - "--api"
[...] [...]
- "--consul" - "--consul"
- "--consul.endpoint=consul:8500" - "--consul.endpoint=consul:8500"
@ -199,19 +203,20 @@ services:
image: traefik:1.5 image: traefik:1.5
command: command:
- "storeconfig" - "storeconfig"
- "--web" - "--api"
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https" - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
- "--entrypoints=Name:https Address::443 TLS" - "--entrypoints=Name:https Address::443 TLS"
- "--defaultentrypoints=http,https" - "--defaultentrypoints=http,https"
- "--acme" - "--acme"
- "--acme.storage=traefik/acme/account" - "--acme.storage=traefik/acme/account"
- "--acme.entryPoint=https" - "--acme.entryPoint=https"
- "--acme.httpChallenge.entryPoint=http"
- "--acme.OnHostRule=true" - "--acme.OnHostRule=true"
- "--acme.onDemand=false" - "--acme.onDemand=false"
- "--acme.email=contact@jmaitrehenry.ca" - "--acme.email=foobar@example.com"
- "--docker" - "--docker"
- "--docker.swarmmode" - "--docker.swarmmode"
- "--docker.domain=jmaitrehenry.ca" - "--docker.domain=example.com"
- "--docker.watch" - "--docker.watch"
- "--consul" - "--consul"
- "--consul.endpoint=consul:8500" - "--consul.endpoint=consul:8500"

View file

@ -104,6 +104,8 @@ email = "your-email-here@my-awesome-app.org"
storage = "acme.json" storage = "acme.json"
entryPoint = "https" entryPoint = "https"
OnHostRule = true OnHostRule = true
[acme.httpChallenge]
entryPoint = "http"
``` ```
This is the minimum configuration required to do the following: This is the minimum configuration required to do the following:

View file

@ -6,6 +6,7 @@ You will find here some configuration examples of Træfik.
```toml ```toml
defaultEntryPoints = ["http"] defaultEntryPoints = ["http"]
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
@ -15,6 +16,7 @@ defaultEntryPoints = ["http"]
```toml ```toml
defaultEntryPoints = ["http", "https"] defaultEntryPoints = ["http", "https"]
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
@ -34,6 +36,7 @@ Note that we can either give path to certificate file or directly the file conte
```toml ```toml
defaultEntryPoints = ["http", "https"] defaultEntryPoints = ["http", "https"]
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
@ -52,10 +55,16 @@ defaultEntryPoints = ["http", "https"]
## Let's Encrypt support ## 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 ```toml
[entryPoints] [entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https] [entryPoints.https]
address = ":443" address = ":443"
[entryPoints.https.tls] [entryPoints.https.tls]
@ -65,6 +74,8 @@ email = "test@traefik.io"
storage = "acme.json" storage = "acme.json"
caServer = "http://172.18.0.1:4000/directory" caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https" entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
[[acme.domains]] [[acme.domains]]
main = "local1.com" main = "local1.com"
@ -78,14 +89,16 @@ entryPoint = "https"
main = "local4.com" 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. 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 ```toml
[entryPoints] [entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https] [entryPoints.https]
address = ":443" address = ":443"
[entryPoints.https.tls] [entryPoints.https.tls]
@ -96,6 +109,8 @@ storage = "acme.json"
onHostRule = true onHostRule = true
caServer = "http://172.18.0.1:4000/directory" caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https" entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
[[acme.domains]] [[acme.domains]]
main = "local1.com" main = "local1.com"
@ -109,16 +124,18 @@ entryPoint = "https"
main = "local4.com" 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. 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. 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 ```toml
[entryPoints] [entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https] [entryPoints.https]
address = ":443" address = ":443"
[entryPoints.https.tls] [entryPoints.https.tls]
@ -129,9 +146,11 @@ storage = "acme.json"
onDemand = true onDemand = true
caServer = "http://172.18.0.1:4000/directory" caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https" 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 !!! note
@ -153,10 +172,11 @@ This configuration allows generating a Let's Encrypt certificate during the firs
[acme] [acme]
email = "test@traefik.io" email = "test@traefik.io"
storage = "acme.json" storage = "acme.json"
dnsProvider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
delayDontCheckDNS = 0
caServer = "http://172.18.0.1:4000/directory" caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https" entryPoint = "https"
[acme.dnsChallenge]
provider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
delayBeforeCheck = 0
[[acme.domains]] [[acme.domains]]
main = "local1.com" main = "local1.com"
@ -173,12 +193,14 @@ entryPoint = "https"
DNS challenge needs environment variables to be executed. DNS challenge needs environment variables to be executed.
This variables have to be set on the machine/container which host Traefik. 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 ```toml
[entryPoints] [entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https] [entryPoints.https]
address = ":443" address = ":443"
[entryPoints.https.tls] [entryPoints.https.tls]
@ -192,10 +214,11 @@ storage = "acme.json"
onHostRule = true onHostRule = true
caServer = "http://172.18.0.1:4000/directory" caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https" 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 ### Cluster mode
@ -207,6 +230,8 @@ Before you use Let's Encrypt in a Traefik cluster, take a look to [the key-value
```toml ```toml
[entryPoints] [entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https] [entryPoints.https]
address = ":443" address = ":443"
[entryPoints.https.tls] [entryPoints.https.tls]
@ -217,6 +242,9 @@ storage = "traefik/acme/account"
caServer = "http://172.18.0.1:4000/directory" caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https" entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
[[acme.domains]] [[acme.domains]]
main = "local1.com" main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"] sans = ["test1.local1.com", "test2.local1.com"]
@ -244,10 +272,12 @@ The `consul` provider contains the configuration.
```toml ```toml
[frontends] [frontends]
[frontends.frontend1] [frontends.frontend1]
backend = "backend2" backend = "backend2"
[frontends.frontend1.routes.test_1] [frontends.frontend1.routes.test_1]
rule = "Host:test.localhost" rule = "Host:test.localhost"
[frontends.frontend2] [frontends.frontend2]
backend = "backend1" backend = "backend1"
passHostHeader = true passHostHeader = true
@ -255,6 +285,7 @@ The `consul` provider contains the configuration.
entrypoints = ["https"] # overrides defaultEntryPoints entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1] [frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost" rule = "Host:{subdomain:[a-z]+}.localhost"
[frontends.frontend3] [frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2" backend = "backend2"
@ -272,6 +303,7 @@ Passwords are encoded in MD5: you can use htpasswd to generate those ones.
```toml ```toml
defaultEntryPoints = ["http"] defaultEntryPoints = ["http"]
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
@ -286,6 +318,7 @@ via a configurable header value.
```toml ```toml
defaultEntryPoints = ["http"] defaultEntryPoints = ["http"]
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
@ -306,7 +339,7 @@ idleTimeout = "360s"
## Securing Ping Health Check ## Securing Ping Health Check
The `/ping` health-check URL is enabled together with the web admin panel, enabled with the command-line `--web` or config file option `[web]`. The `/ping` health-check URL is enabled with the command-line `--ping` or config file option `[ping]`.
Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows: Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows:
* Regular path: `http://hostname:80/foo` * Regular path: `http://hostname:80/foo`

View file

@ -57,8 +57,7 @@ RootCAs = [ "./backend.cert" ]
keyFile = "./frontend.key" keyFile = "./frontend.key"
[web] [api]
address = ":8080"
[file] [file]

View file

@ -1,6 +1,6 @@
# Kubernetes Ingress Controller # Kubernetes Ingress Controller
This guide explains how to use Træfik as an Ingress controller in a Kubernetes cluster. This guide explains how to use Træfik as an Ingress controller for a Kubernetes cluster.
If you are not familiar with Ingresses in Kubernetes you might want to read the [Kubernetes user guide](https://kubernetes.io/docs/concepts/services-networking/ingress/) If you are not familiar with Ingresses in Kubernetes you might want to read the [Kubernetes user guide](https://kubernetes.io/docs/concepts/services-networking/ingress/)
@ -8,8 +8,10 @@ The config files used in this guide can be found in the [examples directory](htt
## Prerequisites ## Prerequisites
1. A working Kubernetes cluster. If you want to follow along with this guide, you should setup [minikube](https://kubernetes.io/docs/getting-started-guides/minikube/) 1. A working Kubernetes cluster. If you want to follow along with this guide, you should setup [minikube](https://kubernetes.io/docs/getting-started-guides/minikube/) on your machine, as it is the quickest way to get a local Kubernetes cluster setup for experimentation and development.
on your machine, as it is the quickest way to get a local Kubernetes cluster setup for experimentation and development.
!!! note
The guide is likely not fully adequate for a production-ready setup.
2. The `kubectl` binary should be [installed on your workstation](https://kubernetes.io/docs/getting-started-guides/minikube/#download-kubectl). 2. The `kubectl` binary should be [installed on your workstation](https://kubernetes.io/docs/getting-started-guides/minikube/#download-kubectl).
@ -117,7 +119,7 @@ spec:
- image: traefik - image: traefik
name: traefik-ingress-lb name: traefik-ingress-lb
args: args:
- --web - --api
- --kubernetes - --kubernetes
--- ---
kind: Service kind: Service
@ -137,6 +139,7 @@ spec:
name: admin name: admin
type: NodePort type: NodePort
``` ```
[examples/k8s/traefik-deployment.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-deployment.yaml) [examples/k8s/traefik-deployment.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-deployment.yaml)
!!! note !!! note
@ -182,7 +185,7 @@ spec:
privileged: true privileged: true
args: args:
- -d - -d
- --web - --api
- --kubernetes - --kubernetes
--- ---
kind: Service kind: Service
@ -233,7 +236,7 @@ Start by listing the pods in the `kube-system` namespace:
kubectl --namespace=kube-system get pods kubectl --namespace=kube-system get pods
``` ```
``` ```shell
NAME READY STATUS RESTARTS AGE NAME READY STATUS RESTARTS AGE
kube-addon-manager-minikubevm 1/1 Running 0 4h kube-addon-manager-minikubevm 1/1 Running 0 4h
kubernetes-dashboard-s8krj 1/1 Running 0 4h kubernetes-dashboard-s8krj 1/1 Running 0 4h
@ -250,19 +253,21 @@ _It might take a few moments for kubernetes to pull the Træfik image and start
You should now be able to access Træfik on port 80 of your Minikube instance when using the DaemonSet: You should now be able to access Træfik on port 80 of your Minikube instance when using the DaemonSet:
```sh ```shell
curl $(minikube ip) curl $(minikube ip)
``` ```
```
```shell
404 page not found 404 page not found
``` ```
If you decided to use the deployment, then you need to target the correct NodePort, which can be seen when you execute `kubectl get services --namespace=kube-system`. If you decided to use the deployment, then you need to target the correct NodePort, which can be seen when you execute `kubectl get services --namespace=kube-system`.
```sh ```shell
curl $(minikube ip):<NODEPORT> curl $(minikube ip):<NODEPORT>
``` ```
```
```shell
404 page not found 404 page not found
``` ```
@ -273,19 +278,20 @@ All further examples below assume a DaemonSet installation. Deployment users wil
## Deploy Træfik using Helm Chart ## Deploy Træfik using Helm Chart
Instead of installing Træfik via an own object, you can also use the Træfik Helm chart. !!! note
The Helm Chart is maintained by the community, not the Traefik project maintainers.
This allows more complex configuration via Kubernetes [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configmap/) and enabled TLS certificates. Instead of installing Træfik via Kubernetes object directly, you can also use the Træfik Helm chart.
Install Træfik chart by: Install the Træfik chart by:
```shell ```shell
helm install stable/traefik helm install stable/traefik
``` ```
For more information, check out [the doc](https://github.com/kubernetes/charts/tree/master/stable/traefik). For more information, check out [the documentation](https://github.com/kubernetes/charts/tree/master/stable/traefik).
## Submitting An Ingress to the cluster. ## Submitting an Ingress to the Cluster
Lets start by creating a Service and an Ingress that will expose the [Træfik Web UI](https://github.com/containous/traefik#web-ui). Lets start by creating a Service and an Ingress that will expose the [Træfik Web UI](https://github.com/containous/traefik#web-ui).
@ -318,22 +324,23 @@ spec:
serviceName: traefik-web-ui serviceName: traefik-web-ui
servicePort: 80 servicePort: 80
``` ```
[examples/k8s/ui.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/ui.yaml) [examples/k8s/ui.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/ui.yaml)
```shell ```shell
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/ui.yaml kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/ui.yaml
``` ```
Now lets setup an entry in our /etc/hosts file to route `traefik-ui.minikube` to our cluster. Now lets setup an entry in our `/etc/hosts` file to route `traefik-ui.minikube` to our cluster.
In production you would want to set up real dns entries. In production you would want to set up real DNS entries.
You can get the ip address of your minikube instance by running `minikube ip` You can get the IP address of your minikube instance by running `minikube ip`:
```shell ```shell
echo "$(minikube ip) traefik-ui.minikube" | sudo tee -a /etc/hosts echo "$(minikube ip) traefik-ui.minikube" | sudo tee -a /etc/hosts
``` ```
We should now be able to visit [traefik-ui.minikube](http://traefik-ui.minikube) in the browser and view the Træfik Web UI. We should now be able to visit [traefik-ui.minikube](http://traefik-ui.minikube) in the browser and view the Træfik web UI.
### Add a TLS Certificate to the Ingress ### Add a TLS Certificate to the Ingress
@ -382,11 +389,9 @@ If there are any errors while loading the TLS section of an ingress, the whole i
## Basic Authentication ## Basic Authentication
It's possible to add additional authentication annotations in the Ingress rule. It's possible to protect access to Traefik through basic authentication. (See the [Kubernetes Ingress](/configuration/backends/kubernetes) configuration page for syntactical details and restrictions.)
The source of the authentication is a secret that contains usernames and passwords inside the key auth.
To read about basic auth limitations see the [Kubernetes Ingress](/configuration/backends/kubernetes) configuration page.
#### Creating the Secret ### Creating the Secret
A. Use `htpasswd` to create a file containing the username and the base64-encoded password: A. Use `htpasswd` to create a file containing the username and the base64-encoded password:
@ -400,25 +405,28 @@ You will be prompted for a password which you will have to enter twice.
```shell ```shell
cat auth cat auth
``` ```
```
```shell
myusername:$apr1$78Jyn/1K$ERHKVRPPlzAX8eBtLuvRZ0 myusername:$apr1$78Jyn/1K$ERHKVRPPlzAX8eBtLuvRZ0
``` ```
B. Now use `kubectl` to create a secret in the monitoring namespace using the file created by `htpasswd`. B. Now use `kubectl` to create a secret in the `monitoring` namespace using the file created by `htpasswd`.
```shell ```shell
kubectl create secret generic mysecret --from-file auth --namespace=monitoring kubectl create secret generic mysecret --from-file auth --namespace=monitoring
``` ```
!!! note !!! note
Secret must be in same namespace as the ingress rule. Secret must be in same namespace as the Ingress object.
C. Create the ingress using the following annotations to specify basic auth and that the username and password is stored in `mysecret`. C. Attach the following annotations to the Ingress object:
- `ingress.kubernetes.io/auth-type: "basic"` - `ingress.kubernetes.io/auth-type: "basic"`
- `ingress.kubernetes.io/auth-secret: "mysecret"` - `ingress.kubernetes.io/auth-secret: "mysecret"`
Following is a full ingress example based on Prometheus: They specify basic authentication and reference the Secret `mysecret` containing the credentials.
Following is a full Ingress example based on Prometheus:
```yaml ```yaml
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
@ -440,17 +448,17 @@ spec:
servicePort: 9090 servicePort: 9090
``` ```
You can apply the example ingress as following: You can apply the example as following:
```shell ```shell
kubectl create -f prometheus-ingress.yaml -n monitoring kubectl create -f prometheus-ingress.yaml -n monitoring
``` ```
## Name based routing ## Name-based Routing
In this example we are going to setup websites for 3 of the United Kingdoms best loved cheeses, Cheddar, Stilton and Wensleydale. In this example we are going to setup websites for three of the United Kingdoms best loved cheeses: Cheddar, Stilton, and Wensleydale.
First lets start by launching the 3 pods for the cheese websites. First lets start by launching the pods for the cheese websites.
```yaml ```yaml
--- ---
@ -532,13 +540,14 @@ spec:
ports: ports:
- containerPort: 80 - containerPort: 80
``` ```
[examples/k8s/cheese-deployments.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-deployments.yaml) [examples/k8s/cheese-deployments.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-deployments.yaml)
```shell ```shell
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-deployments.yaml kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-deployments.yaml
``` ```
Next we need to setup a service for each of the cheese pods. Next we need to setup a Service for each of the cheese pods.
```yaml ```yaml
--- ---
@ -587,7 +596,6 @@ spec:
!!! note !!! note
We also set a [circuit breaker expression](/basics/#backends) for one of the backends by setting the `traefik.backend.circuitbreaker` annotation on the service. We also set a [circuit breaker expression](/basics/#backends) for one of the backends by setting the `traefik.backend.circuitbreaker` annotation on the service.
[examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml) [examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml)
```shell ```shell
@ -627,6 +635,7 @@ spec:
serviceName: wensleydale serviceName: wensleydale
servicePort: http servicePort: http
``` ```
[examples/k8s/cheese-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-ingress.yaml) [examples/k8s/cheese-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-ingress.yaml)
!!! note !!! note
@ -637,7 +646,7 @@ kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/exa
``` ```
Now visit the [Træfik dashboard](http://traefik-ui.minikube/) and you should see a frontend for each host. Now visit the [Træfik dashboard](http://traefik-ui.minikube/) and you should see a frontend for each host.
Along with a backend listing for each service with a Server set up for each pod. Along with a backend listing for each service with a server set up for each pod.
If you edit your `/etc/hosts` again you should be able to access the cheese websites in your browser. If you edit your `/etc/hosts` again you should be able to access the cheese websites in your browser.
@ -645,11 +654,11 @@ If you edit your `/etc/hosts` again you should be able to access the cheese webs
echo "$(minikube ip) stilton.minikube cheddar.minikube wensleydale.minikube" | sudo tee -a /etc/hosts echo "$(minikube ip) stilton.minikube cheddar.minikube wensleydale.minikube" | sudo tee -a /etc/hosts
``` ```
* [Stilton](http://stilton.minikube/) - [Stilton](http://stilton.minikube/)
* [Cheddar](http://cheddar.minikube/) - [Cheddar](http://cheddar.minikube/)
* [Wensleydale](http://wensleydale.minikube/) - [Wensleydale](http://wensleydale.minikube/)
## Path based routing ## Path-based Routing
Now lets suppose that our fictional client has decided that while they are super happy about our cheesy web design, when they asked for 3 websites they had not really bargained on having to buy 3 domain names. Now lets suppose that our fictional client has decided that while they are super happy about our cheesy web design, when they asked for 3 websites they had not really bargained on having to buy 3 domain names.
@ -681,10 +690,11 @@ spec:
serviceName: wensleydale serviceName: wensleydale
servicePort: http servicePort: http
``` ```
[examples/k8s/cheeses-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheeses-ingress.yaml) [examples/k8s/cheeses-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheeses-ingress.yaml)
!!! note !!! note
we are configuring Træfik to strip the prefix from the url path with the `traefik.frontend.rule.type` annotation so that we can use the containers from the previous example without modification. We are configuring Træfik to strip the prefix from the url path with the `traefik.frontend.rule.type` annotation so that we can use the containers from the previous example without modification.
```shell ```shell
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheeses-ingress.yaml kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheeses-ingress.yaml
@ -696,14 +706,14 @@ echo "$(minikube ip) cheeses.minikube" | sudo tee -a /etc/hosts
You should now be able to visit the websites in your browser. You should now be able to visit the websites in your browser.
* [cheeses.minikube/stilton](http://cheeses.minikube/stilton/) - [cheeses.minikube/stilton](http://cheeses.minikube/stilton/)
* [cheeses.minikube/cheddar](http://cheeses.minikube/cheddar/) - [cheeses.minikube/cheddar](http://cheeses.minikube/cheddar/)
* [cheeses.minikube/wensleydale](http://cheeses.minikube/wensleydale/) - [cheeses.minikube/wensleydale](http://cheeses.minikube/wensleydale/)
## Specifying priority for routing ## Specifying Routing Priorities
Sometimes you need to specify priority for ingress route, especially when handling wildcard routes. Sometimes you need to specify priority for ingress routes, especially when handling wildcard routes.
This can be done by adding annotation `traefik.frontend.priority`, i.e.: This can be done by adding the `traefik.frontend.priority` annotation, i.e.:
```yaml ```yaml
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
@ -738,7 +748,7 @@ spec:
servicePort: http servicePort: http
``` ```
Note that priority values must be quoted to avoid them being interpreted as numbers (which are illegal for annotations). Note that priority values must be quoted to avoid numeric interpretation (which are illegal for annotations).
## Forwarding to ExternalNames ## Forwarding to ExternalNames
@ -746,26 +756,25 @@ When specifying an [ExternalName](https://kubernetes.io/docs/concepts/services-n
Træfik will forward requests to the given host accordingly and use HTTPS when the Service port matches 443. Træfik will forward requests to the given host accordingly and use HTTPS when the Service port matches 443.
This still requires setting up a proper port mapping on the Service from the Ingress port to the (external) Service port. This still requires setting up a proper port mapping on the Service from the Ingress port to the (external) Service port.
## Disable passing the Host header ## Disable passing the Host Header
By default Træfik will pass the incoming Host header on to the upstream resource. By default Træfik will pass the incoming Host header to the upstream resource.
There are times however where you may not want this to be the case. However, there are times when you may not want this to be the case. For example, if your service is of the ExternalName type.
For example if your service is of the ExternalName type.
### Disable entirely ### Disable globally
Add the following to your toml config: Add the following to your TOML configuration file:
```toml ```toml
disablePassHostHeaders = true disablePassHostHeaders = true
``` ```
### Disable per ingress ### Disable per Ingress
To disable passing the Host header per ingress resource set the `traefik.frontend.passHostHeader` annotation on your ingress to `false`. To disable passing the Host header per ingress resource set the `traefik.frontend.passHostHeader` annotation on your ingress to `"false"`.
Here is an example ingress definition: Here is an example definition:
```yaml ```yaml
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
@ -801,12 +810,11 @@ spec:
externalName: static.otherdomain.com externalName: static.otherdomain.com
``` ```
If you were to visit `example.com/static` the request would then be passed onto `static.otherdomain.com/static` and s`tatic.otherdomain.com` would receive the request with the Host header being `static.otherdomain.com`. If you were to visit `example.com/static` the request would then be passed on to `static.otherdomain.com/static`, and `static.otherdomain.com` would receive the request with the Host header being `static.otherdomain.com`.
!!! note !!! note
The per ingress annotation overides whatever the global value is set to. The per-ingress annotation overrides whatever the global value is set to.
So you could set `disablePassHostHeaders` to `true` in your toml file and then enable passing So you could set `disablePassHostHeaders` to `true` in your TOML configuration file and then enable passing the host header per ingress if you wanted.
the host header per ingress if you wanted.
## Partitioning the Ingress object space ## Partitioning the Ingress object space

View file

@ -70,10 +70,13 @@ logLevel = "DEBUG"
defaultEntryPoints = ["http", "https"] defaultEntryPoints = ["http", "https"]
[entryPoints] [entryPoints]
[entryPoints.api]
address = ":8081"
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"
[entryPoints.https] [entryPoints.https]
address = ":443" address = ":443"
[entryPoints.https.tls] [entryPoints.https.tls]
[[entryPoints.https.tls.certificates]] [[entryPoints.https.tls.certificates]]
certFile = "integration/fixtures/https/snitest.com.cert" certFile = "integration/fixtures/https/snitest.com.cert"
@ -94,8 +97,8 @@ defaultEntryPoints = ["http", "https"]
watch = true watch = true
prefix = "traefik" prefix = "traefik"
[web] [api]
address = ":8081" entrypoint = "api"
``` ```
And there, the same global configuration in the Key-value Store (using `prefix = "traefik"`): And there, the same global configuration in the Key-value Store (using `prefix = "traefik"`):
@ -105,6 +108,7 @@ And there, the same global configuration in the Key-value Store (using `prefix =
| `/traefik/loglevel` | `DEBUG` | | `/traefik/loglevel` | `DEBUG` |
| `/traefik/defaultentrypoints/0` | `http` | | `/traefik/defaultentrypoints/0` | `http` |
| `/traefik/defaultentrypoints/1` | `https` | | `/traefik/defaultentrypoints/1` | `https` |
| `/traefik/entrypoints/api/address` | `:8081` |
| `/traefik/entrypoints/http/address` | `:80` | | `/traefik/entrypoints/http/address` | `:80` |
| `/traefik/entrypoints/https/address` | `:443` | | `/traefik/entrypoints/https/address` | `:443` |
| `/traefik/entrypoints/https/tls/certificates/0/certfile` | `integration/fixtures/https/snitest.com.cert` | | `/traefik/entrypoints/https/tls/certificates/0/certfile` | `integration/fixtures/https/snitest.com.cert` |
@ -115,7 +119,7 @@ And there, the same global configuration in the Key-value Store (using `prefix =
| `/traefik/consul/endpoint` | `127.0.0.1:8500` | | `/traefik/consul/endpoint` | `127.0.0.1:8500` |
| `/traefik/consul/watch` | `true` | | `/traefik/consul/watch` | `true` |
| `/traefik/consul/prefix` | `traefik` | | `/traefik/consul/prefix` | `traefik` |
| `/traefik/web/address` | `:8081` | | `/traefik/api/entrypoint` | `api` |
In case you are setting key values manually: In case you are setting key values manually:

View file

@ -90,7 +90,7 @@ docker-machine ssh manager "docker service create \
--docker.swarmmode \ --docker.swarmmode \
--docker.domain=traefik \ --docker.domain=traefik \
--docker.watch \ --docker.watch \
--web" --api"
``` ```
Let's explain this command: Let's explain this command:
@ -102,7 +102,7 @@ Let's explain this command:
| `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock` | we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. | | `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock` | we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. |
| `--network traefik-net` | we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. | | `--network traefik-net` | we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. |
| `--docker` | enable docker backend, and `--docker.swarmmode` to enable the swarm mode on Træfik. | | `--docker` | enable docker backend, and `--docker.swarmmode` to enable the swarm mode on Træfik. |
| `--web` | activate the webUI on port 8080 | | `--api | activate the webUI on port 8080 |
## Deploy your apps ## Deploy your apps

View file

@ -93,7 +93,7 @@ docker $(docker-machine config mhs-demo0) run \
--docker.tls.key=/ssl/server-key.pem \ --docker.tls.key=/ssl/server-key.pem \
--docker.tls.insecureSkipVerify \ --docker.tls.insecureSkipVerify \
--docker.watch \ --docker.watch \
--web --api
``` ```
Let's explain this command: Let's explain this command:
@ -107,7 +107,7 @@ Let's explain this command:
| `--docker` | enable docker backend | | `--docker` | enable docker backend |
| `--docker.endpoint=tcp://172.18.0.1:2376` | connect to the swarm master using the docker_gwbridge network | | `--docker.endpoint=tcp://172.18.0.1:2376` | connect to the swarm master using the docker_gwbridge network |
| `--docker.tls` | enable TLS using the docker-machine keys | | `--docker.tls` | enable TLS using the docker-machine keys |
| `--web` | activate the webUI on port 8080 | | `--api` | activate the webUI on port 8080 |
## Deploy your apps ## Deploy your apps

View file

@ -19,6 +19,8 @@ entryPoint = "https"
onDemand = false onDemand = false
OnHostRule = true OnHostRule = true
caServer = "http://traefik.localhost.com:4000/directory" caServer = "http://traefik.localhost.com:4000/directory"
[acme.httpChallenge]
entryPoint="http"
[web] [web]

View file

@ -78,6 +78,7 @@ services :
- "80:80" - "80:80"
- "443:443" - "443:443"
- "5001:443" # Needed for SNI challenge - "5001:443" # Needed for SNI challenge
- "5002:80" # Needed for HTTP challenge
expose: expose:
- "8080" - "8080"
labels: labels:

View file

@ -136,11 +136,13 @@ services:
expose: expose:
- "443" - "443"
- "5001" - "5001"
- "5002"
ports: ports:
- "80:80" - "80:80"
- "8080:8080" - "8080:8080"
- "443:443" - "443:443"
- "5001:443" # Needed for SNI challenge - "5001:443" # Needed for SNI challenge
- "5002:80" # Needed for HTTP challenge
networks: networks:
net: net:
ipv4_address: 10.0.1.8 ipv4_address: 10.0.1.8
@ -157,6 +159,7 @@ services:
expose: expose:
- "443" - "443"
- "5001" - "5001"
- "5002"
ports: ports:
- "88:80" - "88:80"
- "8888:8080" - "8888:8080"

View file

@ -15,6 +15,8 @@ storage = "traefik/acme/account"
entryPoint = "https" entryPoint = "https"
OnHostRule = true OnHostRule = true
caServer = "http://traefik.boulder.com:4000/directory" caServer = "http://traefik.boulder.com:4000/directory"
[acme.httpChallenge]
entryPoint="http"
[web] [web]

View file

@ -49,6 +49,7 @@ type BackendHealthCheck struct {
//HealthCheck struct //HealthCheck struct
type HealthCheck struct { type HealthCheck struct {
mutex sync.Mutex
Backends map[string]*BackendHealthCheck Backends map[string]*BackendHealthCheck
cancel context.CancelFunc cancel context.CancelFunc
} }
@ -77,14 +78,16 @@ func NewBackendHealthCheck(options Options, backendName string) *BackendHealthCh
//SetBackendsConfiguration set backends configuration //SetBackendsConfiguration set backends configuration
func (hc *HealthCheck) SetBackendsConfiguration(parentCtx context.Context, backends map[string]*BackendHealthCheck) { func (hc *HealthCheck) SetBackendsConfiguration(parentCtx context.Context, backends map[string]*BackendHealthCheck) {
hc.mutex.Lock()
hc.Backends = backends hc.Backends = backends
if hc.cancel != nil { if hc.cancel != nil {
hc.cancel() hc.cancel()
} }
ctx, cancel := context.WithCancel(parentCtx) ctx, cancel := context.WithCancel(parentCtx)
hc.cancel = cancel hc.cancel = cancel
hc.mutex.Unlock()
for _, backend := range hc.Backends { for _, backend := range backends {
currentBackend := backend currentBackend := backend
safe.Go(func() { safe.Go(func() {
hc.execute(ctx, currentBackend) hc.execute(ctx, currentBackend)

View file

@ -72,6 +72,26 @@ func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificate(c *check.C) {
s.retrieveAcmeCertificate(c, testCase) 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 // Test OnDemand option with a wildcard provided certificate
func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateWithWildcard(c *check.C) { func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateWithWildcard(c *check.C) {
testCase := AcmeTestCase{ testCase := AcmeTestCase{

View file

@ -294,7 +294,10 @@ func (s *ConsulSuite) skipTestGlobalConfigurationWithClientTLS(c *check.C) {
s.setupConsulTLS(c) s.setupConsulTLS(c)
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
err := s.kv.Put("traefik/web/address", []byte(":8081"), nil) err := s.kv.Put("traefik/api/entrypoint", []byte("api"), nil)
c.Assert(err, checker.IsNil)
err = s.kv.Put("traefik/entrypoints/api/address", []byte(":8081"), nil)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// wait for consul // wait for consul
@ -341,7 +344,7 @@ func (s *ConsulSuite) TestCommandStoreConfig(c *check.C) {
"/traefik/loglevel": "DEBUG", "/traefik/loglevel": "DEBUG",
"/traefik/defaultentrypoints/0": "http", "/traefik/defaultentrypoints/0": "http",
"/traefik/entrypoints/http/address": ":8000", "/traefik/entrypoints/http/address": ":8000",
"/traefik/web/address": ":8080", "/traefik/api/entrypoint": "traefik",
"/traefik/consul/endpoint": consulHost + ":8500", "/traefik/consul/endpoint": consulHost + ":8500",
} }

View file

@ -411,7 +411,7 @@ func (s *Etcd3Suite) TestCommandStoreConfig(c *check.C) {
"/traefik/loglevel": "DEBUG", "/traefik/loglevel": "DEBUG",
"/traefik/defaultentrypoints/0": "http", "/traefik/defaultentrypoints/0": "http",
"/traefik/entrypoints/http/address": ":8000", "/traefik/entrypoints/http/address": ":8000",
"/traefik/web/address": ":8080", "/traefik/api/entrypoint": "traefik",
"/traefik/etcd/endpoint": ipEtcd + ":4001", "/traefik/etcd/endpoint": ipEtcd + ":4001",
} }

View file

@ -419,7 +419,7 @@ func (s *EtcdSuite) TestCommandStoreConfig(c *check.C) {
"/traefik/loglevel": "DEBUG", "/traefik/loglevel": "DEBUG",
"/traefik/defaultentrypoints/0": "http", "/traefik/defaultentrypoints/0": "http",
"/traefik/entrypoints/http/address": ":8000", "/traefik/entrypoints/http/address": ":8000",
"/traefik/web/address": ":8080", "/traefik/api/entrypoint": "traefik",
"/traefik/etcd/endpoint": etcdHost + ":4001", "/traefik/etcd/endpoint": etcdHost + ":4001",
} }

View file

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

View file

@ -5,6 +5,8 @@ logLevel = "DEBUG"
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[entryPoints.api]
address = ":8081"
[consul] [consul]
@ -12,5 +14,5 @@ logLevel = "DEBUG"
watch = true watch = true
prefix = "traefik" prefix = "traefik"
[web] [api]
address = ":8081" entryPoint = "api"

View file

@ -3,6 +3,8 @@ defaultEntryPoints = ["http","https"]
logLevel = "DEBUG" logLevel = "DEBUG"
[entryPoints] [entryPoints]
[entryPoints.api]
address = ":8081"
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[entryPoints.https] [entryPoints.https]
@ -16,5 +18,5 @@ logLevel = "DEBUG"
prefix = "traefik" prefix = "traefik"
watch = true watch = true
[web] [api]
address = ":8081" entryPoint = "api"

View file

@ -1,8 +1,7 @@
defaultEntryPoints = ["http"] defaultEntryPoints = ["http"]
logLevel = "DEBUG" logLevel = "DEBUG"
[web] [api]
address = ":8080"
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]

View file

@ -6,7 +6,7 @@ logLevel = "DEBUG"
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[web] [api]
[docker] [docker]

View file

@ -5,6 +5,8 @@ logLevel = "DEBUG"
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":8080" address = ":8080"
[entryPoints.api]
address = ":8081"
[dynamodb] [dynamodb]
AccessKeyID = "key" AccessKeyID = "key"
@ -12,5 +14,5 @@ logLevel = "DEBUG"
Endpoint = "{{.DynamoURL}}" Endpoint = "{{.DynamoURL}}"
Region = "us-east-1" Region = "us-east-1"
[web] [api]
address = ":8081" entryPoint = "api"

View file

@ -5,6 +5,8 @@ logLevel = "DEBUG"
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[entryPoints.api]
address = ":8081"
[etcd] [etcd]
@ -13,5 +15,5 @@ logLevel = "DEBUG"
watch = true watch = true
useAPIV3 = {{.UseAPIV3}} useAPIV3 = {{.UseAPIV3}}
[web] [api]
address = ":8081" entryPoint = "api"

View file

@ -3,6 +3,8 @@ defaultEntryPoints = ["http","https"]
logLevel = "DEBUG" logLevel = "DEBUG"
[entryPoints] [entryPoints]
[entryPoints.api]
address = ":8081"
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[entryPoints.https] [entryPoints.https]
@ -16,5 +18,6 @@ logLevel = "DEBUG"
# prefix = "/traefik" # prefix = "/traefik"
# watch = true # watch = true
[web]
address = ":8081" [api]
entryPoint = "api"

View file

@ -10,5 +10,4 @@ logLevel = "DEBUG"
[eureka] [eureka]
endpoint = "http://{{.EurekaHost}}:8761/eureka" endpoint = "http://{{.EurekaHost}}:8761/eureka"
delay = "1s" delay = "1s"
[web] [api]
address = ":8080"

View file

@ -11,8 +11,7 @@ RootCAs = [ """{{ .CertContent }}""" ]
keyFile = """{{ .KeyContent }}""" keyFile = """{{ .KeyContent }}"""
[web] [api]
address = ":8080"
[file] [file]

View file

@ -11,8 +11,7 @@ InsecureSkipVerify = true
keyFile = """{{ .KeyContent }}""" keyFile = """{{ .KeyContent }}"""
[web] [api]
address = ":8080"
[file] [file]

View file

@ -8,8 +8,7 @@ logLevel = "DEBUG"
[entryPoints.http2] [entryPoints.http2]
address = ":9000" address = ":9000"
[web] [api]
address = ":8080"
[file] [file]
[backends] [backends]

View file

@ -8,8 +8,7 @@ logLevel = "DEBUG"
[entryPoints.http2] [entryPoints.http2]
address = ":9000" address = ":9000"
[web] [api]
address = ":8080"
[file] [file]
[backends] [backends]

View file

@ -6,8 +6,7 @@ logLevel = "DEBUG"
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[web] [api]
address = ":8080"
[file] [file]
[backends] [backends]

View file

@ -6,8 +6,7 @@ logLevel = "DEBUG"
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[web] [api]
address = ":8080"
[file] [file]
[backends] [backends]

View file

@ -16,8 +16,7 @@ defaultEntryPoints = ["https"]
certFile = "fixtures/https/snitest.org.cert" certFile = "fixtures/https/snitest.org.cert"
keyFile = "fixtures/https/snitest.org.key" keyFile = "fixtures/https/snitest.org.key"
[web] [api]
address = ":8080"
[file] [file]

View file

@ -15,8 +15,7 @@ defaultEntryPoints = ["https"]
certFile = "fixtures/https/snitest.org.cert" certFile = "fixtures/https/snitest.org.cert"
keyFile = "fixtures/https/snitest.org.key" keyFile = "fixtures/https/snitest.org.key"
[web] [api]
address = ":8080"
[file] [file]

View file

@ -16,8 +16,7 @@ defaultEntryPoints = ["https"]
certFile = "fixtures/https/snitest.org.cert" certFile = "fixtures/https/snitest.org.cert"
keyFile = "fixtures/https/snitest.org.key" keyFile = "fixtures/https/snitest.org.key"
[web] [api]
address = ":8080"
[file] [file]

View file

@ -10,8 +10,7 @@ defaultEntryPoints = ["https"]
address = ":8443" address = ":8443"
[entryPoints.https02.tls] [entryPoints.https02.tls]
[web] [api]
address = ":8080"
[file] [file]

View file

@ -13,8 +13,7 @@ defaultEntryPoints = ["https"]
certFile = "fixtures/https/snitest.org.cert" certFile = "fixtures/https/snitest.org.cert"
keyFile = "fixtures/https/snitest.org.key" keyFile = "fixtures/https/snitest.org.key"
[web] [api]
address = ":8080"
[file] [file]

View file

@ -24,8 +24,7 @@ fblo6RBxUQ==
[entryPoints.http] [entryPoints.http]
address = ":8081" address = ":8081"
[web] [api]
address = ":8080"
[file] [file]

View file

@ -9,8 +9,7 @@ RootCAs = [ "fixtures/https/rootcas/local.crt"]
[entryPoints.http] [entryPoints.http]
address = ":8081" address = ":8081"
[web] [api]
address = ":8080"
[file] [file]

View file

@ -8,14 +8,16 @@ defaultEntryPoints = ["http"]
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[entryPoints.api]
address = ":7888"
checkNewVersion = false checkNewVersion = false
################################################################ ################################################################
# Web configuration backend # Api configuration backend
################################################################ ################################################################
[web] [api]
address = ":7888" entryPoint = "api"
################################################################ ################################################################
# File configuration backend # File configuration backend

View file

@ -5,9 +5,11 @@ logLevel = "DEBUG"
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[entryPoints.api]
address = ":9090"
[web] [api]
address = ":9090" entryPoint = "api"
[marathon] [marathon]
endpoint = "{{.MarathonURL}}" endpoint = "{{.MarathonURL}}"

View file

@ -7,8 +7,7 @@ address = ":8000"
[entryPoints.http.proxyProtocol] [entryPoints.http.proxyProtocol]
trustedIPs = ["{{.HaproxyIP}}"] trustedIPs = ["{{.HaproxyIP}}"]
[web] [api]
address = ":8080"
[file] [file]

View file

@ -7,8 +7,7 @@ address = ":8000"
[entryPoints.http.proxyProtocol] [entryPoints.http.proxyProtocol]
trustedIPs = ["1.2.3.4"] trustedIPs = ["1.2.3.4"]
[web] [api]
address = ":8080"
[file] [file]

View file

@ -5,5 +5,4 @@ defaultEntryPoints = ["http"]
[entryPoints.http] [entryPoints.http]
address = ":8000" address = ":8000"
[web] [api]
address = ":8080"

View file

@ -8,8 +8,7 @@ defaultEntryPoints = ["http"]
[accessLog] [accessLog]
format = "json" format = "json"
[web] [api]
address = ":8080"
[forwardingTimeouts] [forwardingTimeouts]
dialTimeout = "300ms" dialTimeout = "300ms"

View file

@ -7,8 +7,7 @@ logLevel = "DEBUG"
address = ":8000" address = ":8000"
[web] [api]
address = ":8080"
[file] [file]

View file

@ -11,8 +11,7 @@ InsecureSkipVerify=true
certFile = "resources/tls/local.cert" certFile = "resources/tls/local.cert"
keyFile = "resources/tls/local.key" keyFile = "resources/tls/local.key"
[web] [api]
address = ":8080"
[file] [file]

View file

@ -0,0 +1,5 @@
whoami1:
image: emilevauge/whoami
labels:
- traefik.enable=true
- traefik.frontend.rule=AddPrefix:/whoami;PathPrefix:/

View file

@ -2,6 +2,7 @@ package docker
import ( import (
"context" "context"
"io"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
@ -218,18 +219,24 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
} }
eventsc, errc := dockerClient.Events(ctx, options) eventsc, errc := dockerClient.Events(ctx, options)
for event := range eventsc { for {
select {
case event := <-eventsc:
if event.Action == "start" || if event.Action == "start" ||
event.Action == "die" || event.Action == "die" ||
strings.HasPrefix(event.Action, "health_status") { strings.HasPrefix(event.Action, "health_status") {
startStopHandle(event) startStopHandle(event)
} }
case err := <-errc:
if err == io.EOF {
log.Debug("Provider event stream closed")
} }
if err := <-errc; err != nil {
return err return err
} }
} }
} }
}
return nil return nil
} }
notify := func(err error, time time.Duration) { notify := func(err error, time time.Duration) {

View file

@ -82,11 +82,11 @@ func (p *Provider) newK8sClient() (Client, error) {
} }
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" { 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) 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) return NewExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath)
} }

View file

@ -770,6 +770,10 @@ func (s *Server) addInternalPublicRoutes(entryPointName string, router *mux.Rout
if s.globalConfiguration.Ping != nil && s.globalConfiguration.Ping.EntryPoint != "" && s.globalConfiguration.Ping.EntryPoint == entryPointName { if s.globalConfiguration.Ping != nil && s.globalConfiguration.Ping.EntryPoint != "" && s.globalConfiguration.Ping.EntryPoint == entryPointName {
s.globalConfiguration.Ping.AddRoutes(router) 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) { func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher, middlewares []negroni.Handler, internalMiddlewares []negroni.Handler) (*http.Server, net.Listener, error) {

View file

@ -39,7 +39,7 @@ type DigestAuth struct {
ClientCacheTolerance int ClientCacheTolerance int
clients map[string]*digest_client clients map[string]*digest_client
mutex sync.Mutex mutex sync.RWMutex
} }
// check that DigestAuth implements AuthenticatorInterface // check that DigestAuth implements AuthenticatorInterface
@ -84,11 +84,16 @@ func (a *DigestAuth) Purge(count int) {
(or requires reauthentication). (or requires reauthentication).
*/ */
func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
a.mutex.Lock()
defer a.mutex.Unlock()
if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance { if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
a.Purge(a.ClientCacheTolerance * 2) a.Purge(a.ClientCacheTolerance * 2)
} }
nonce := RandomKey() nonce := RandomKey()
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
w.Header().Set(contentType, a.Headers.V().UnauthContentType) w.Header().Set(contentType, a.Headers.V().UnauthContentType)
w.Header().Set(a.Headers.V().Authenticate, w.Header().Set(a.Headers.V().Authenticate,
fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`, fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
@ -118,8 +123,8 @@ func DigestAuthParams(authorization string) map[string]string {
Authentication-Info response header. Authentication-Info response header.
*/ */
func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) { func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) {
da.mutex.Lock() da.mutex.RLock()
defer da.mutex.Unlock() defer da.mutex.RUnlock()
username = "" username = ""
authinfo = nil authinfo = nil
auth := DigestAuthParams(r.Header.Get(da.Headers.V().Authorization)) auth := DigestAuthParams(r.Header.Get(da.Headers.V().Authorization))
@ -242,6 +247,8 @@ func (a *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc {
// NewContext returns a context carrying authentication information for the request. // NewContext returns a context carrying authentication information for the request.
func (a *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context { func (a *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
a.mutex.Lock()
defer a.mutex.Unlock()
username, authinfo := a.CheckAuth(r) username, authinfo := a.CheckAuth(r)
info := &Info{Username: username, ResponseHeaders: make(http.Header)} info := &Info{Username: username, ResponseHeaders: make(http.Header)}
if username != "" { if username != "" {