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
*.exe
.DS_Store
/example/acme/acme.json
/examples/acme/acme.json

View file

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

View file

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

View file

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

View file

@ -7,6 +7,8 @@ import (
"fmt"
"io/ioutil"
fmtlog "log"
"net"
"net/http"
"os"
"regexp"
"strings"
@ -14,6 +16,8 @@ import (
"github.com/BurntSushi/ty/fun"
"github.com/cenk/backoff"
"github.com/containous/flaeg"
"github.com/containous/mux"
"github.com/containous/staert"
"github.com/containous/traefik/cluster"
"github.com/containous/traefik/log"
@ -33,25 +37,39 @@ var (
// ACME allows to connect to lets encrypt and retrieve certs
type ACME struct {
Email string `description:"Email address used for registration"`
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
Storage string `description:"File or key used for certificates storage."`
StorageFile string // deprecated
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
DNSProvider string `description:"Use a DNS based challenge provider rather than HTTPS."`
DelayDontCheckDNS int `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
ACMELogging bool `description:"Enable debug logging of ACME actions."`
client *acme.Client
defaultCertificate *tls.Certificate
store cluster.Store
challengeProvider *challengeProvider
checkOnDemandDomain func(domain string) bool
jobs *channels.InfiniteChannel
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
dynamicCerts *safe.Safe
Email string `description:"Email address used for registration"`
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
Storage string `description:"File or key used for certificates storage."`
StorageFile string // deprecated
OnDemand bool `description:"Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge"`
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge"`
DNSProvider string `description:"Use a DNS-01 acme challenge rather than TLS-SNI-01 challenge."` // deprecated
DelayDontCheckDNS flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // deprecated
ACMELogging bool `description:"Enable debug logging of ACME actions."`
client *acme.Client
defaultCertificate *tls.Certificate
store cluster.Store
challengeTLSProvider *challengeTLSProvider
challengeHTTPProvider *challengeHTTPProvider
checkOnDemandDomain func(domain string) bool
jobs *channels.InfiniteChannel
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
dynamicCerts *safe.Safe
}
// DNSChallenge contains DNS challenge Configuration
type DNSChallenge struct {
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
DelayBeforeCheck flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
}
// HTTPChallenge contains HTTP challenge Configuration
type HTTPChallenge struct {
EntryPoint string `description:"HTTP challenge EntryPoint"`
}
//Domains parse []Domain
@ -107,15 +125,39 @@ func (a *ACME) init() error {
return err
}
a.defaultCertificate = cert
// TODO: to remove in the 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()
return nil
}
// AddRoutes add routes on internal router
func (a *ACME) AddRoutes(router *mux.Router) {
router.Methods(http.MethodGet).
Path(acme.HTTP01ChallengePath("{token}")).
Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if a.challengeHTTPProvider == nil {
rw.WriteHeader(http.StatusNotFound)
return
}
vars := mux.Vars(req)
if token, ok := vars["token"]; ok {
domain, _, err := net.SplitHostPort(req.Host)
if err != nil {
log.Debugf("Unable to split host and port: %v. Fallback to request host.", err)
domain = req.Host
}
tokenValue := a.challengeHTTPProvider.getTokenValue(token, domain)
if len(tokenValue) > 0 {
rw.WriteHeader(http.StatusOK)
rw.Write(tokenValue)
return
}
}
rw.WriteHeader(http.StatusNotFound)
}))
}
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, certs *safe.Safe, checkOnDemandDomain func(domain string) bool) error {
err := a.init()
@ -155,7 +197,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
}
a.store = datastore
a.challengeProvider = &challengeProvider{store: a.store}
a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
ticker := time.NewTicker(24 * time.Hour)
leadership.Pool.AddGoCtx(func(ctx context.Context) {
@ -171,69 +213,69 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
}
})
leadership.AddListener(func(elected bool) error {
if elected {
_, err := a.store.Load()
leadership.AddListener(a.leadershipListener)
return nil
}
func (a *ACME) leadershipListener(elected bool) error {
if elected {
_, err := a.store.Load()
if err != nil {
return err
}
transaction, object, err := a.store.Begin()
if err != nil {
return err
}
account := object.(*Account)
account.Init()
var needRegister bool
if account == nil || len(account.Email) == 0 {
account, err = NewAccount(a.Email)
if err != nil {
return err
}
transaction, object, err := a.store.Begin()
needRegister = true
}
a.client, err = a.buildACMEClient(account)
if err != nil {
return err
}
if needRegister {
// New users will need to register; be sure to save it
log.Debug("Register...")
reg, err := a.client.Register()
if err != nil {
return err
}
account := object.(*Account)
account.Init()
var needRegister bool
if account == nil || len(account.Email) == 0 {
account, err = NewAccount(a.Email)
if err != nil {
return err
}
needRegister = true
}
account.Registration = reg
}
// The client has a URL to the current Let's Encrypt Subscriber
// Agreement. The user will need to agree to it.
log.Debug("AgreeToTOS...")
err = a.client.AgreeToTOS()
if err != nil {
log.Debug(err)
// Let's Encrypt Subscriber Agreement renew ?
reg, err := a.client.QueryRegistration()
if err != nil {
return err
}
a.client, err = a.buildACMEClient(account)
if err != nil {
return err
}
if needRegister {
// New users will need to register; be sure to save it
log.Debug("Register...")
reg, err := a.client.Register()
if err != nil {
return err
}
account.Registration = reg
}
// The client has a URL to the current Let's Encrypt Subscriber
// Agreement. The user will need to agree to it.
log.Debug("AgreeToTOS...")
account.Registration = reg
err = a.client.AgreeToTOS()
if err != nil {
// Let's Encrypt Subscriber Agreement renew ?
reg, err := a.client.QueryRegistration()
if err != nil {
return err
}
account.Registration = reg
err = a.client.AgreeToTOS()
if err != nil {
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
}
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
}
err = transaction.Commit(account)
if err != nil {
return err
}
a.retrieveCertificates()
a.renewCertificates()
a.runJobs()
}
return nil
})
err = transaction.Commit(account)
if err != nil {
return err
}
a.retrieveCertificates()
a.renewCertificates()
a.runJobs()
}
return nil
}
@ -253,7 +295,7 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, certs *safe.Safe, checkO
a.TLSConfig = tlsConfig
localStore := NewLocalStore(a.Storage)
a.store = localStore
a.challengeProvider = &challengeProvider{store: a.store}
a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
var needRegister bool
var account *Account
@ -337,7 +379,7 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
return providedCertificate, nil
}
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
if challengeCert, ok := a.challengeTLSProvider.getCertificate(domain); ok {
log.Debugf("ACME got challenge %s", domain)
return challengeCert, nil
}
@ -351,7 +393,7 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
}
return a.loadCertificateOnDemand(clientHello)
}
log.Debugf("ACME got nothing %s", domain)
log.Debugf("No certificate found or generated for %s", domain)
return nil, nil
}
@ -472,16 +514,16 @@ func (a *ACME) storeRenewedCertificate(certificateResource *DomainsCertificate,
return nil
}
func dnsOverrideDelay(delay int) error {
func dnsOverrideDelay(delay flaeg.Duration) error {
var err error
if delay > 0 {
log.Debugf("Delaying %d seconds rather than validating DNS propagation", delay)
log.Debugf("Delaying %d rather than validating DNS propagation", delay)
acme.PreCheckDNS = func(_, _ string) (bool, error) {
time.Sleep(time.Duration(delay) * time.Second)
time.Sleep(time.Duration(delay))
return true, nil
}
} else if delay < 0 {
err = fmt.Errorf("invalid negative DelayDontCheckDNS: %d", delay)
err = fmt.Errorf("invalid negative DelayBeforeCheck: %d", delay)
}
return err
}
@ -497,25 +539,29 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
return nil, err
}
if len(a.DNSProvider) > 0 {
log.Debugf("Using DNS Challenge provider: %s", a.DNSProvider)
if a.DNSChallenge != nil && len(a.DNSChallenge.Provider) > 0 {
log.Debugf("Using DNS Challenge provider: %s", a.DNSChallenge.Provider)
err = dnsOverrideDelay(a.DelayDontCheckDNS)
err = dnsOverrideDelay(a.DNSChallenge.DelayBeforeCheck)
if err != nil {
return nil, err
}
var provider acme.ChallengeProvider
provider, err = dns.NewDNSChallengeProviderByName(a.DNSProvider)
provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider)
if err != nil {
return nil, err
}
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
err = client.SetChallengeProvider(acme.DNS01, provider)
} else if a.HTTPChallenge != nil && len(a.HTTPChallenge.EntryPoint) > 0 {
client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01})
a.challengeHTTPProvider = &challengeHTTPProvider{store: a.store}
err = client.SetChallengeProvider(acme.HTTP01, a.challengeHTTPProvider)
} else {
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeTLSProvider)
}
if err != nil {
@ -623,7 +669,7 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
// Get provided certificate which check a domains list (Main and SANs)
// from static and dynamic provided certificates
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)
if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil {
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)
if len(failures) > 0 {
log.Error(failures)
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
return nil, fmt.Errorf("cannot obtain certificates %+v", failures)
}
log.Debugf("Loaded ACME certificates %s", domains)
return &Certificate{

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()
a := ACME{DNSProvider: "manual", DelayDontCheckDNS: 10, CAServer: ts.URL}
a := ACME{DNSChallenge: &DNSChallenge{Provider: "manual", DelayBeforeCheck: 10}, CAServer: ts.URL}
client, err := a.buildACMEClient(account)
if err != nil {

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

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) {
// Expose dashboard
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/").
Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"})))

View file

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

View file

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

View file

@ -243,6 +243,23 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
log.Errorln("Error using file configuration backend, no filename defined")
}
}
if gc.ACME != nil {
// TODO: to remove in the futurs
if len(gc.ACME.StorageFile) > 0 && len(gc.ACME.Storage) == 0 {
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
gc.ACME.Storage = gc.ACME.StorageFile
}
if len(gc.ACME.DNSProvider) > 0 {
log.Warn("ACME.DNSProvider is deprecated, use ACME.DNSChallenge instead")
gc.ACME.DNSChallenge = &acme.DNSChallenge{Provider: gc.ACME.DNSProvider, DelayBeforeCheck: gc.ACME.DelayDontCheckDNS}
}
if gc.ACME.OnDemand {
log.Warn("ACME.OnDemand is deprecated")
}
}
}
// DefaultEntryPoints holds default entry points

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]
address = ":80"
[web]
address = ":8080"
[api]
[Docker]
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]
address = ":80"
[web]
address = ":8080"
[api]
[Docker]
Endpoint = "xxxx"

View file

@ -7,10 +7,14 @@ See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) an
```toml
# Sample entrypoint configuration when using ACME.
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
```
```toml
# Enable ACME (Let's Encrypt): automatic SSL.
[acme]
@ -33,17 +37,16 @@ email = "test@traefik.io"
storage = "acme.json"
# or `storage = "traefik/acme/account"` if using KV store.
# Entrypoint to proxy acme challenge/apply certificates to.
# WARNING, must point to an entrypoint on port 443
# Entrypoint to proxy acme apply certificates to.
# WARNING, if the TLS-SNI-01 challenge is used, it must point to an entrypoint on port 443
#
# Required
#
entryPoint = "https"
# Use a DNS based acme challenge rather than external HTTPS access
# Use a DNS-01 acme challenge rather than TLS-SNI-01 challenge
#
#
# Optional
# Optional (Deprecated, replaced by [acme.dnsChallenge])
#
# dnsProvider = "digitalocean"
@ -51,25 +54,29 @@ entryPoint = "https"
# If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds.
# Useful if internal networks block external DNS queries.
#
# Optional
# Optional (Deprecated, replaced by [acme.dnsChallenge])
# Default: 0
#
# delayDontCheckDNS = 0
# If true, display debug log messages from the acme client library.
#
# Optional
# Default: false
#
# acmeLogging = true
# Enable on demand certificate. (Deprecated)
# Enable on demand certificate generation.
#
# Optional
# Optional (Deprecated)
# Default: false
#
# onDemand = true
# Enable certificate generation on frontends Host rules.
#
# Optional
# Default: false
#
# onHostRule = true
@ -78,26 +85,64 @@ entryPoint = "https"
# - Leave comment to go to prod.
#
# Optional
# Default: "https://acme-v01.api.letsencrypt.org/directory"
#
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
# Domains list.
#
# [[acme.domains]]
# main = "local1.com"
# sans = ["test1.local1.com", "test2.local1.com"]
# main = "local1.com"
# sans = ["test1.local1.com", "test2.local1.com"]
# [[acme.domains]]
# main = "local2.com"
# sans = ["test1.local2.com", "test2.local2.com"]
# main = "local2.com"
# sans = ["test1.local2.com", "test2.local2.com"]
# [[acme.domains]]
# main = "local3.com"
# main = "local3.com"
# [[acme.domains]]
# main = "local4.com"
# main = "local4.com"
# Use a HTTP-01 acme challenge rather than TLS-SNI-01 challenge
#
# Optional but recommend
#
[acme.httpChallenge]
# EntryPoint to use for the challenges.
#
# Required
#
entryPoint = "http"
# Use a DNS-01 acme challenge rather than TLS-SNI-01 challenge
#
# Optional
#
# [acme.dnsChallenge]
# Provider used.
#
# Required
#
# provider = "digitalocean"
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
# If delayBeforeCheck is greater than zero, avoid this & instead just wait so many seconds.
# Useful if internal networks block external DNS queries.
#
# Optional
# Default: 0
#
# delayBeforeCheck = 0
```
!!! note
Even if `TLS-SNI-01` challenge is [disabled](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188) for the moment, it stays the _by default_ ACME Challenge in Træfik.
If `TLS-SNI-01` challenge is not re-enabled in the future, it we will be removed from Træfik.
!!! note
ACME entryPoint has to be relied to the port 443, otherwise ACME Challenges can not be done.
It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
If `TLS-SNI-01` challenge is used, `acme.entryPoint` has to be reachable by Let's Encrypt through the port 443.
If `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through the port 80.
These are Let's Encrypt limitations as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
### `storage`
@ -110,7 +155,7 @@ storage = "acme.json"
File or key used for certificates storage.
**WARNING** If you use Træfik in Docker, you have 2 options:
**WARNING:** If you use Træfik in Docker, you have 2 options:
- create a file on your host and mount it as a volume:
```toml
@ -133,19 +178,60 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
!!! note
During Træfik configuration migration from a configuration file to a KV store (thanks to `storeconfig` subcommand as described [here](/user-guide/kv-config/#store-configuration-in-key-value-store)), if ACME certificates have to be migrated too, use both `storageFile` and `storage`.
`storageFile` will contain the path to the `acme.json` file to migrate.
`storage` will contain the key where the certificates will be stored.
### `dnsProvider`
- `storageFile` will contain the path to the `acme.json` file to migrate.
- `storage` will contain the key where the certificates will be stored.
### `acme.httpChallenge`
Use `HTTP-01` challenge to generate/renew ACME certificates.
```toml
[acme]
# ...
dnsProvider = "digitalocean"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
```
#### `entryPoint`
Specify the entryPoint to use during the challenges.
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
# ...
[acme]
# ...
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
```
!!! note
`acme.httpChallenge.entryPoint` has to be reachable by Let's Encrypt through the port 80.
It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
### `acme.dnsChallenge`
Use `DNS-01` challenge to generate/renew ACME certificates.
```toml
[acme]
# ...
[acme.dnsChallenge]
provider = "digitalocean"
delayBeforeCheck = 0
# ...
```
Use a DNS based acme challenge rather than external HTTPS access, e.g. for a firewalled server.
#### `provider`
Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables to enable setting it:
@ -164,7 +250,7 @@ Select the provider that matches the DNS domain that will host the challenge TXT
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` |
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` |
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` |
| manual | - | none, but run Træfik interactively & turn on `acmeLogging` to see instructions & press <kbd>Enter</kbd>. |
| manual | - | none, but run Træfik interactively & turn on `acmeLogging` to see instructions & press <kbd>Enter</kbd>. |
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` |
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` |
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` |
@ -175,22 +261,21 @@ Select the provider that matches the DNS domain that will host the challenge TXT
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or configured user/instance IAM profile. |
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` |
### `delayDontCheckDNS`
#### `delayBeforeCheck`
```toml
[acme]
# ...
delayDontCheckDNS = 0
# ...
```
By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify.
If `delayDontCheckDNS` is greater than zero, avoid this & instead just wait so many seconds.
By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
If `delayBeforeCheck` is greater than zero, avoid this & instead just wait so many seconds.
Useful if internal networks block external DNS queries.
!!! note
This field has no sense if a `provider` is not defined.
### `onDemand` (Deprecated)
!!! warning
This option is deprecated.
```toml
[acme]
# ...
@ -208,9 +293,6 @@ This will request a certificate from Let's Encrypt during the first TLS handshak
!!! warning
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
!!! warning
This option is deprecated.
### `onHostRule`
```toml
@ -240,21 +322,21 @@ CA server to use.
- Uncomment the line to run on the staging Let's Encrypt server.
- Leave comment to go to prod.
### `domains`
### `acme.domains`
```toml
[acme]
# ...
[[acme.domains]]
main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"]
main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"]
[[acme.domains]]
main = "local2.com"
sans = ["test1.local2.com", "test2.local2.com"]
main = "local2.com"
sans = ["test1.local2.com", "test2.local2.com"]
[[acme.domains]]
main = "local3.com"
main = "local3.com"
[[acme.domains]]
main = "local4.com"
main = "local4.com"
# ...
```
@ -265,3 +347,15 @@ All domains must have A/AAAA records pointing to Træfik.
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
Each domain & SANs will lead to a certificate request.
### `dnsProvider` (Deprecated)
!!! warning
This option is deprecated.
Please refer to [DNS challenge provider section](/configuration/acme/#provider)
### `delayDontCheckDNS` (Deprecated)
!!! warning
This option is deprecated.
Please refer to [DNS challenge delayBeforeCheck section](/configuration/acme/#delaybeforecheck)

View file

@ -161,7 +161,7 @@ curl -s "http://localhost:8080/health" | jq .
// average response time in seconds
"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
"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).
## Configuration
```toml
@ -44,7 +43,7 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
#
# 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
# Default: empty (process all Ingresses)
@ -75,30 +74,33 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
### `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.
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`
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.
## Annotations
Annotations can be used on containers to override default behaviour for the whole Ingress resource:
### General annotations
- `traefik.frontend.rule.type: PathPrefixStrip`
The following general annotations are applicable on the Ingress object:
- `traefik.frontend.rule.type: PathPrefixStrip`
Override the default frontend rule type. Default: `PathPrefix`.
- `traefik.frontend.priority: "3"`
Override the default frontend rule priority.
@ -108,50 +110,41 @@ Annotations can be used on containers to override default behaviour for the whol
Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.replacement`.
- `traefik.frontend.redirect.replacement: http://mydomain/$1`:
Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.regex`.
- `traefik.frontend.entryPoints: http,https`
- `traefik.frontend.entryPoints: http,https`
Override the default frontend endpoints.
- `traefik.frontend.passTLSCert: true`
- `traefik.frontend.passTLSCert: true`
Override the default frontend PassTLSCert value. Default: `false`.
- `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.
- `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
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`
Override the default `wrr` load balancer algorithm.
- `traefik.backend.loadbalancer.stickiness=true`
Enable backend sticky sessions.
- `traefik.backend.loadbalancer.stickiness.cookieName=NAME`
Manually set the cookie name for sticky sessions.
- `traefik.backend.loadbalancer.sticky=true`
Enable backend sticky sessions (DEPRECATED).
- `traefik.backend.circuitbreaker: <expression>`
Set the circuit breaker expression for the backend.
- `traefik.backend.loadbalancer.method=drr`
Override the default `wrr` load balancer algorithm
- `traefik.backend.loadbalancer.stickiness=true`
Enable backend sticky sessions
- `traefik.backend.loadbalancer.stickiness.cookieName=NAME`
Manually set the cookie name for sticky sessions
- `traefik.backend.loadbalancer.sticky=true`
Enable backend sticky sessions (DEPRECATED)
### Security annotations
Additionally, an annotation can be used on Kubernetes services to set the [circuit breaker expression](/basics/#backends) for a backend.
The following security annotations are applicable on the Ingress object:
- `traefik.backend.circuitbreaker: <expression>`
Set the circuit breaker expression for the backend. Default: `nil`.
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:
- `ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"`
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/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/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-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. |
@ -171,17 +164,17 @@ The following security annotations can be applied to the ingress object to add s
### Authentication
Is possible to add additional authentication annotations in the Ingress rule.
The source of the authentication is a secret that contains usernames and passwords inside the key auth.
Is possible to add additional authentication annotations to the Ingress object.
The source of the authentication is a Secret object that contains the credentials.
- `ingress.kubernetes.io/auth-type`: `basic`
- `ingress.kubernetes.io/auth-secret`: `mysecret`
Contains the usernames and passwords with access to the paths defined in the Ingress Rule.
Contains the authentication type. The only permitted type is `basic`.
- `ingress.kubernetes.io/auth-secret`: `mysecret`
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.
- Realm not configurable; only `traefik` default.
- Secret must contain only single file.
- The realm is not configurable; the only supported (and default) value is `traefik`.
- The Secret must contain a single file only.

View file

@ -35,6 +35,15 @@ address = ":8080"
# Default: false
#
readOnly = true
# Set the root path for webui and API
#
# Deprecated
# Optional
#
# path = "/mypath"
#
```
## 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
[web.auth.digest]
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
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.http]
address = ":80"
[entryPoints.http.auth.basic]
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
[entryPoints.http.auth.digest]
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
usersFile = "/path/to/.htdigest"
```

View file

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

View file

@ -56,15 +56,18 @@ $ traefik \
--acme \
--acme.storage=/etc/traefik/acme/acme.json \
--acme.entryPoint=https \
--acme.httpChallenge.entryPoint=http \
--acme.email=contact@mydomain.ca
```
Let's Encrypt needs 3 parameters: an entry point to listen to, a storage for certificates, and an email for the registration.
Let's Encrypt needs 4 parameters: an TLS entry point to listen to, a non-TLS entry point to allow HTTP challenges, a storage for certificates, and an email for the registration.
To enable Let's Encrypt support, you need to add `--acme` flag.
Now, Træfik needs to know where to store the certificates, we can choose between a key in a Key-Value store, or a file path: `--acme.storage=my/key` or `--acme.storage=/path/to/acme.json`.
The `acme.httpChallenge.entryPoint` flag enables the `HTTP-01` challenge and specifies the entryPoint to use during the challenges.
For your email and the entry point, it's `--acme.entryPoint` and `--acme.email` flags.
### Docker configuration
@ -90,13 +93,14 @@ services:
traefik:
image: traefik:1.5
command:
- "--web"
- "--api"
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
- "--entrypoints=Name:https Address::443 TLS"
- "--defaultentrypoints=http,https"
- "--acme"
- "--acme.storage=/etc/traefik/acme/acme.json"
- "--acme.entryPoint=https"
- "--acme.httpChallenge.entryPoint=http"
- "--acme.OnHostRule=true"
- "--acme.onDemand=false"
- "--acme.email=contact@mydomain.ca"
@ -155,7 +159,7 @@ The initializer in a docker-compose file will be:
image: traefik:1.5
command:
- "storeconfig"
- "--web"
- "--api"
[...]
- "--consul"
- "--consul.endpoint=consul:8500"
@ -199,19 +203,20 @@ services:
image: traefik:1.5
command:
- "storeconfig"
- "--web"
- "--api"
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
- "--entrypoints=Name:https Address::443 TLS"
- "--defaultentrypoints=http,https"
- "--acme"
- "--acme.storage=traefik/acme/account"
- "--acme.entryPoint=https"
- "--acme.httpChallenge.entryPoint=http"
- "--acme.OnHostRule=true"
- "--acme.onDemand=false"
- "--acme.email=contact@jmaitrehenry.ca"
- "--acme.email=foobar@example.com"
- "--docker"
- "--docker.swarmmode"
- "--docker.domain=jmaitrehenry.ca"
- "--docker.domain=example.com"
- "--docker.watch"
- "--consul"
- "--consul.endpoint=consul:8500"

View file

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

View file

@ -6,6 +6,7 @@ You will find here some configuration examples of Træfik.
```toml
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":80"
@ -15,6 +16,7 @@ defaultEntryPoints = ["http"]
```toml
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
@ -34,6 +36,7 @@ Note that we can either give path to certificate file or directly the file conte
```toml
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
@ -52,10 +55,16 @@ defaultEntryPoints = ["http", "https"]
## Let's Encrypt support
### Basic example
!!! note
Even if `TLS-SNI-01` challenge is [disabled](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188), for the moment, it stays the _by default_ ACME Challenge in Træfik but all the examples use the `HTTP-01` challenge (except DNS challenge examples).
If `TLS-SNI-01` challenge is not re-enabled in the future, it we will be removed from Træfik.
### Basic example with HTTP challenge
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@ -65,6 +74,8 @@ email = "test@traefik.io"
storage = "acme.json"
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
[[acme.domains]]
main = "local1.com"
@ -78,14 +89,16 @@ entryPoint = "https"
main = "local4.com"
```
This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com` with described SANs.
This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com` with described SANs.
Traefik generates these certificates when it starts and it needs to be restart if new domains are added.
### OnHostRule option
### OnHostRule option (with HTTP challenge)
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@ -96,6 +109,8 @@ storage = "acme.json"
onHostRule = true
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
[[acme.domains]]
main = "local1.com"
@ -109,16 +124,18 @@ entryPoint = "https"
main = "local4.com"
```
This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com`.
This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com`.
Traefik generates these certificates when it starts.
If a backend is added with a `onHost` rule, Traefik will automatically generate the Let's Encrypt certificate for the new domain.
### OnDemand option
### OnDemand option (with HTTP challenge)
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@ -129,9 +146,11 @@ storage = "acme.json"
onDemand = true
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
```
This configuration allows generating a Let's Encrypt certificate during the first HTTPS request on a new domain.
This configuration allows generating a Let's Encrypt certificate (thanks to `HTTP-01` challenge) during the first HTTPS request on a new domain.
!!! note
@ -153,10 +172,11 @@ This configuration allows generating a Let's Encrypt certificate during the firs
[acme]
email = "test@traefik.io"
storage = "acme.json"
dnsProvider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
delayDontCheckDNS = 0
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
[acme.dnsChallenge]
provider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
delayBeforeCheck = 0
[[acme.domains]]
main = "local1.com"
@ -173,12 +193,14 @@ entryPoint = "https"
DNS challenge needs environment variables to be executed.
This variables have to be set on the machine/container which host Traefik.
These variables are described [in this section](/configuration/acme/#dnsprovider).
These variables are described [in this section](/configuration/acme/#provider).
### OnHostRule option and provided certificates
### OnHostRule option and provided certificates (with HTTP challenge)
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@ -192,10 +214,11 @@ storage = "acme.json"
onHostRule = true
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
```
Traefik will only try to generate a Let's encrypt certificate if the domain cannot be checked by the provided certificates.
Traefik will only try to generate a Let's encrypt certificate (thanks to `HTTP-01` challenge) if the domain cannot be checked by the provided certificates.
### Cluster mode
@ -207,6 +230,8 @@ Before you use Let's Encrypt in a Traefik cluster, take a look to [the key-value
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
@ -217,6 +242,9 @@ storage = "traefik/acme/account"
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
[[acme.domains]]
main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"]
@ -244,10 +272,12 @@ The `consul` provider contains the configuration.
```toml
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
@ -255,10 +285,11 @@ The `consul` provider contains the configuration.
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path:/test"
rule = "Path:/test"
```
## Enable Basic authentication in an entrypoint
@ -272,6 +303,7 @@ Passwords are encoded in MD5: you can use htpasswd to generate those ones.
```toml
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":80"
@ -286,6 +318,7 @@ via a configurable header value.
```toml
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":80"
@ -306,7 +339,7 @@ idleTimeout = "360s"
## 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:
* Regular path: `http://hostname:80/foo`

View file

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

View file

@ -1,6 +1,6 @@
# 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/)
@ -8,8 +8,10 @@ The config files used in this guide can be found in the [examples directory](htt
## 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/)
on your machine, as it is the quickest way to get a local Kubernetes cluster setup for experimentation and development.
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.
!!! 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).
@ -79,8 +81,8 @@ For namespaced restrictions, one RoleBinding is required per watched namespace a
It is possible to use Træfik with a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or a [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) object,
whereas both options have their own pros and cons:
- The scalability is much better when using a Deployment, because you will have a Single-Pod-per-Node model when using the DeaemonSet.
- It is possible to exclusively run a Service on a dedicated set of machines using taints and tolerations with a DaemonSet.
- The scalability is much better when using a Deployment, because you will have a Single-Pod-per-Node model when using the DeaemonSet.
- It is possible to exclusively run a Service on a dedicated set of machines using taints and tolerations with a DaemonSet.
- On the other hand the DaemonSet allows you to access any Node directly on Port 80 and 443, where you have to setup a [Service](https://kubernetes.io/docs/concepts/services-networking/service/) object with a Deployment.
The Deployment objects looks like this:
@ -117,7 +119,7 @@ spec:
- image: traefik
name: traefik-ingress-lb
args:
- --web
- --api
- --kubernetes
---
kind: Service
@ -137,6 +139,7 @@ spec:
name: admin
type: NodePort
```
[examples/k8s/traefik-deployment.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-deployment.yaml)
!!! note
@ -182,7 +185,7 @@ spec:
privileged: true
args:
- -d
- --web
- --api
- --kubernetes
---
kind: Service
@ -233,7 +236,7 @@ Start by listing the pods in the `kube-system` namespace:
kubectl --namespace=kube-system get pods
```
```
```shell
NAME READY STATUS RESTARTS AGE
kube-addon-manager-minikubevm 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:
```sh
```shell
curl $(minikube ip)
```
```
```shell
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`.
```sh
```shell
curl $(minikube ip):<NODEPORT>
```
```
```shell
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
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
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).
@ -318,22 +324,23 @@ spec:
serviceName: traefik-web-ui
servicePort: 80
```
[examples/k8s/ui.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/ui.yaml)
```shell
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.
You can get the ip address of your minikube instance by running `minikube ip`
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`:
```shell
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
@ -382,11 +389,9 @@ If there are any errors while loading the TLS section of an ingress, the whole i
## Basic Authentication
It's possible to add additional authentication annotations in the Ingress rule.
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.
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.)
#### Creating the Secret
### Creating the Secret
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
cat auth
```
```
```shell
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
kubectl create secret generic mysecret --from-file auth --namespace=monitoring
```
!!! 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-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
apiVersion: extensions/v1beta1
@ -440,17 +448,17 @@ spec:
servicePort: 9090
```
You can apply the example ingress as following:
You can apply the example as following:
```shell
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
---
@ -532,13 +540,14 @@ spec:
ports:
- containerPort: 80
```
[examples/k8s/cheese-deployments.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-deployments.yaml)
```shell
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
---
@ -587,7 +596,6 @@ spec:
!!! 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.
[examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml)
```shell
@ -627,6 +635,7 @@ spec:
serviceName: wensleydale
servicePort: http
```
[examples/k8s/cheese-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-ingress.yaml)
!!! 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.
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.
@ -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
```
* [Stilton](http://stilton.minikube/)
* [Cheddar](http://cheddar.minikube/)
* [Wensleydale](http://wensleydale.minikube/)
- [Stilton](http://stilton.minikube/)
- [Cheddar](http://cheddar.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.
@ -681,10 +690,11 @@ spec:
serviceName: wensleydale
servicePort: http
```
[examples/k8s/cheeses-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheeses-ingress.yaml)
!!! 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
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.
* [cheeses.minikube/stilton](http://cheeses.minikube/stilton/)
* [cheeses.minikube/cheddar](http://cheeses.minikube/cheddar/)
* [cheeses.minikube/wensleydale](http://cheeses.minikube/wensleydale/)
- [cheeses.minikube/stilton](http://cheeses.minikube/stilton/)
- [cheeses.minikube/cheddar](http://cheeses.minikube/cheddar/)
- [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.
This can be done by adding annotation `traefik.frontend.priority`, i.e.:
Sometimes you need to specify priority for ingress routes, especially when handling wildcard routes.
This can be done by adding the `traefik.frontend.priority` annotation, i.e.:
```yaml
apiVersion: extensions/v1beta1
@ -738,34 +748,33 @@ spec:
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
When specifying an [ExternalName](https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors),
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.
## 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.
For example if your service is of the ExternalName type.
However, there are times when you may not want this to be the case. 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
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
apiVersion: extensions/v1beta1
@ -801,12 +810,11 @@ spec:
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
The per ingress annotation overides whatever the global value is set to.
So you could set `disablePassHostHeaders` to `true` in your toml file and then enable passing
the host header per ingress if you wanted.
The per-ingress annotation overrides whatever the global value is set to.
So you could set `disablePassHostHeaders` to `true` in your TOML configuration file and then enable passing the host header per ingress if you wanted.
## Partitioning the Ingress object space

View file

@ -70,10 +70,13 @@ logLevel = "DEBUG"
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.api]
address = ":8081"
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
certFile = "integration/fixtures/https/snitest.com.cert"
@ -94,8 +97,8 @@ defaultEntryPoints = ["http", "https"]
watch = true
prefix = "traefik"
[web]
address = ":8081"
[api]
entrypoint = "api"
```
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/defaultentrypoints/0` | `http` |
| `/traefik/defaultentrypoints/1` | `https` |
| `/traefik/entrypoints/api/address` | `:8081` |
| `/traefik/entrypoints/http/address` | `:80` |
| `/traefik/entrypoints/https/address` | `:443` |
| `/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/watch` | `true` |
| `/traefik/consul/prefix` | `traefik` |
| `/traefik/web/address` | `:8081` |
| `/traefik/api/entrypoint` | `api` |
In case you are setting key values manually:

View file

@ -90,7 +90,7 @@ docker-machine ssh manager "docker service create \
--docker.swarmmode \
--docker.domain=traefik \
--docker.watch \
--web"
--api"
```
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. |
| `--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. |
| `--web` | activate the webUI on port 8080 |
| `--api | activate the webUI on port 8080 |
## 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.insecureSkipVerify \
--docker.watch \
--web
--api
```
Let's explain this command:
@ -107,7 +107,7 @@ Let's explain this command:
| `--docker` | enable docker backend |
| `--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 |
| `--web` | activate the webUI on port 8080 |
| `--api` | activate the webUI on port 8080 |
## Deploy your apps

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -72,6 +72,26 @@ func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificate(c *check.C) {
s.retrieveAcmeCertificate(c, testCase)
}
// Test OnDemand option with none provided certificate and challenge HTTP-01
func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateHTTP01(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_http01.toml",
onDemand: true,
domainToCheck: acmeDomain}
s.retrieveAcmeCertificate(c, testCase)
}
// Test OnHostRule option with none provided certificate and challenge HTTP-01
func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificateHTTP01(c *check.C) {
testCase := AcmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_http01.toml",
onDemand: false,
domainToCheck: acmeDomain}
s.retrieveAcmeCertificate(c, testCase)
}
// Test OnDemand option with a wildcard provided certificate
func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateWithWildcard(c *check.C) {
testCase := AcmeTestCase{

View file

@ -294,7 +294,10 @@ func (s *ConsulSuite) skipTestGlobalConfigurationWithClientTLS(c *check.C) {
s.setupConsulTLS(c)
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)
// wait for consul
@ -341,7 +344,7 @@ func (s *ConsulSuite) TestCommandStoreConfig(c *check.C) {
"/traefik/loglevel": "DEBUG",
"/traefik/defaultentrypoints/0": "http",
"/traefik/entrypoints/http/address": ":8000",
"/traefik/web/address": ":8080",
"/traefik/api/entrypoint": "traefik",
"/traefik/consul/endpoint": consulHost + ":8500",
}

View file

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

View file

@ -419,7 +419,7 @@ func (s *EtcdSuite) TestCommandStoreConfig(c *check.C) {
"/traefik/loglevel": "DEBUG",
"/traefik/defaultentrypoints/0": "http",
"/traefik/entrypoints/http/address": ":8000",
"/traefik/web/address": ":8080",
"/traefik/api/entrypoint": "traefik",
"/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.http]
address = ":8000"
[entryPoints.api]
address = ":8081"
[consul]
@ -12,5 +14,5 @@ logLevel = "DEBUG"
watch = true
prefix = "traefik"
[web]
address = ":8081"
[api]
entryPoint = "api"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,8 +11,7 @@ InsecureSkipVerify=true
certFile = "resources/tls/local.cert"
keyFile = "resources/tls/local.key"
[web]
address = ":8080"
[api]
[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 (
"context"
"io"
"net"
"net/http"
"strconv"
@ -218,16 +219,22 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
}
eventsc, errc := dockerClient.Events(ctx, options)
for event := range eventsc {
if event.Action == "start" ||
event.Action == "die" ||
strings.HasPrefix(event.Action, "health_status") {
startStopHandle(event)
for {
select {
case event := <-eventsc:
if event.Action == "start" ||
event.Action == "die" ||
strings.HasPrefix(event.Action, "health_status") {
startStopHandle(event)
}
case err := <-errc:
if err == io.EOF {
log.Debug("Provider event stream closed")
}
return err
}
}
if err := <-errc; err != nil {
return err
}
}
}
return nil

View file

@ -82,11 +82,11 @@ func (p *Provider) newK8sClient() (Client, error) {
}
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" {
log.Infof("Creating in-cluster Provider client%s\n", withEndpoint)
log.Infof("Creating in-cluster Provider client%s", withEndpoint)
return NewInClusterClient(p.Endpoint)
}
log.Infof("Creating cluster-external Provider client%s\n", withEndpoint)
log.Infof("Creating cluster-external Provider client%s", withEndpoint)
return NewExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath)
}

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 {
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) {

View file

@ -39,7 +39,7 @@ type DigestAuth struct {
ClientCacheTolerance int
clients map[string]*digest_client
mutex sync.Mutex
mutex sync.RWMutex
}
// check that DigestAuth implements AuthenticatorInterface
@ -84,11 +84,16 @@ func (a *DigestAuth) Purge(count int) {
(or requires reauthentication).
*/
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 {
a.Purge(a.ClientCacheTolerance * 2)
}
nonce := RandomKey()
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
w.Header().Set(contentType, a.Headers.V().UnauthContentType)
w.Header().Set(a.Headers.V().Authenticate,
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.
*/
func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) {
da.mutex.Lock()
defer da.mutex.Unlock()
da.mutex.RLock()
defer da.mutex.RUnlock()
username = ""
authinfo = nil
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.
func (a *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
a.mutex.Lock()
defer a.mutex.Unlock()
username, authinfo := a.CheckAuth(r)
info := &Info{Username: username, ResponseHeaders: make(http.Header)}
if username != "" {