Merge branch '1.5.0-rc5' into master
This commit is contained in:
commit
89d90de7d8
74 changed files with 914 additions and 385 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -11,4 +11,4 @@
|
||||||
*.log
|
*.log
|
||||||
*.exe
|
*.exe
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/example/acme/acme.json
|
/examples/acme/acme.json
|
||||||
|
|
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -1,5 +1,28 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## [v1.5.0-rc5](https://github.com/containous/traefik/tree/v1.5.0-rc5) (2018-01-15)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc4...v1.5.0-rc5)
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
- **[acme]** Add Let's Encrypt HTTP Challenge ([#2701](https://github.com/containous/traefik/pull/2701) by [Juliens](https://github.com/Juliens))
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[acme,logs]** Modify DEBUG messages to get ACME certificates ([#2685](https://github.com/containous/traefik/pull/2685) by [nmengin](https://github.com/nmengin))
|
||||||
|
- **[authentication,middleware]** Fix concurrent map writes on digest auth ([#2695](https://github.com/containous/traefik/pull/2695) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[docker]** Typo in Docker template. ([#2692](https://github.com/containous/traefik/pull/2692) by [ldez](https://github.com/ldez))
|
||||||
|
- **[docker]** Return errors from Docker client.Events ([#2689](https://github.com/containous/traefik/pull/2689) by [BlakeMesdag](https://github.com/BlakeMesdag))
|
||||||
|
- **[kv]** List entries parsing. ([#2669](https://github.com/containous/traefik/pull/2669) by [ldez](https://github.com/ldez))
|
||||||
|
- **[metrics]** Fix data races. ([#2287](https://github.com/containous/traefik/pull/2287) by [tcolgate](https://github.com/tcolgate))
|
||||||
|
- **[middleware]** GzipResponse must implement CloseNotifier if ResponseWriter implement it ([#2657](https://github.com/containous/traefik/pull/2657) by [Juliens](https://github.com/Juliens))
|
||||||
|
- **[websocket]** Add compression and better error handling ([#2702](https://github.com/containous/traefik/pull/2702) by [Juliens](https://github.com/Juliens))
|
||||||
|
- Fix: timeout integration test ([#2679](https://github.com/containous/traefik/pull/2679) by [ldez](https://github.com/ldez))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[cluster]** Add a clustering example with Docker Swarm ([#2589](https://github.com/containous/traefik/pull/2589) by [jmaitrehenry](https://github.com/jmaitrehenry))
|
||||||
|
- **[k8s]** Apply various contentual and stylish improvements to the k8s docs. ([#2677](https://github.com/containous/traefik/pull/2677) by [timoreimann](https://github.com/timoreimann))
|
||||||
|
- **[k8s]** Document rewrite-target annotation. ([#2676](https://github.com/containous/traefik/pull/2676) by [timoreimann](https://github.com/timoreimann))
|
||||||
|
- **[provider,webui]** Fix redirect problem on dashboard + docs/tests on [web] ([#2686](https://github.com/containous/traefik/pull/2686) by [Juliens](https://github.com/Juliens))
|
||||||
|
|
||||||
## [v1.5.0-rc4](https://github.com/containous/traefik/tree/v1.5.0-rc4) (2018-01-04)
|
## [v1.5.0-rc4](https://github.com/containous/traefik/tree/v1.5.0-rc4) (2018-01-04)
|
||||||
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc3...v1.5.0-rc4)
|
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc3...v1.5.0-rc4)
|
||||||
|
|
||||||
|
|
11
Gopkg.lock
generated
11
Gopkg.lock
generated
|
@ -125,10 +125,11 @@
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
branch = "containous-fork"
|
||||||
name = "github.com/abbot/go-http-auth"
|
name = "github.com/abbot/go-http-auth"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "0ddd408d5d60ea76e320503cc7dd091992dee608"
|
revision = "65b0cdae8d7fe5c05c7430e055938ef6d24a66c9"
|
||||||
version = "v0.4.0"
|
source = "github.com/containous/go-http-auth"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/aokoli/goutils"
|
name = "github.com/aokoli/goutils"
|
||||||
|
@ -418,7 +419,7 @@
|
||||||
name = "github.com/docker/leadership"
|
name = "github.com/docker/leadership"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "af20da7d3e62be9259835e93261acf931b5adecf"
|
revision = "af20da7d3e62be9259835e93261acf931b5adecf"
|
||||||
source = "https://github.com/containous/leadership.git"
|
source = "github.com/containous/leadership"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/docker/libcompose"
|
name = "github.com/docker/libcompose"
|
||||||
|
@ -552,7 +553,7 @@
|
||||||
name = "github.com/go-check/check"
|
name = "github.com/go-check/check"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "ca0bf163426aa183d03fd4949101785c0347f273"
|
revision = "ca0bf163426aa183d03fd4949101785c0347f273"
|
||||||
source = "https://github.com/containous/check.git"
|
source = "github.com/containous/check"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/go-ini/ini"
|
name = "github.com/go-ini/ini"
|
||||||
|
@ -1481,6 +1482,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "0f85e152f938507b05ee27ce99b5d2ffe7999d4c27123277a1c398deba0c08b1"
|
inputs-digest = "aa1aa76d15a49bbeee747782a71809fcfbaccf4586e924e70a6d93c51c173a30"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
16
Gopkg.toml
16
Gopkg.toml
|
@ -38,8 +38,9 @@ ignored = ["github.com/sirupsen/logrus"]
|
||||||
name = "github.com/NYTimes/gziphandler"
|
name = "github.com/NYTimes/gziphandler"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
|
branch = "containous-fork"
|
||||||
name = "github.com/abbot/go-http-auth"
|
name = "github.com/abbot/go-http-auth"
|
||||||
version = "0.4.0"
|
source = "github.com/containous/go-http-auth"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -74,8 +75,9 @@ ignored = ["github.com/sirupsen/logrus"]
|
||||||
version = "14.0.0"
|
version = "14.0.0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
name = "github.com/docker/leadership"
|
name = "github.com/docker/leadership"
|
||||||
source = "https://github.com/containous/leadership.git"
|
source = "github.com/containous/leadership"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/docker/libkv"
|
name = "github.com/docker/libkv"
|
||||||
|
@ -92,7 +94,7 @@ ignored = ["github.com/sirupsen/logrus"]
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "fork-containous"
|
branch = "fork-containous"
|
||||||
name = "github.com/go-check/check"
|
name = "github.com/go-check/check"
|
||||||
source = "https://github.com/containous/check.git"
|
source = "github.com/containous/check"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/go-kit/kit"
|
name = "github.com/go-kit/kit"
|
||||||
|
@ -188,15 +190,15 @@ ignored = ["github.com/sirupsen/logrus"]
|
||||||
revision = "6018b68f96b839edfbe3fb48668853f5dbad88a3"
|
revision = "6018b68f96b839edfbe3fb48668853f5dbad88a3"
|
||||||
source = "github.com/ijc25/Gotty"
|
source = "github.com/ijc25/Gotty"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "github.com/gorilla/websocket"
|
||||||
|
revision = "a69d9f6de432e2c6b296a947d8a5ee88f68522cf"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
# ALWAYS keep this override
|
# ALWAYS keep this override
|
||||||
name = "github.com/mailgun/timetools"
|
name = "github.com/mailgun/timetools"
|
||||||
revision = "7e6055773c5137efbeb3bd2410d705fe10ab6bfd"
|
revision = "7e6055773c5137efbeb3bd2410d705fe10ab6bfd"
|
||||||
|
|
||||||
[[override]]
|
|
||||||
name = "github.com/gorilla/websocket"
|
|
||||||
revision = "a69d9f6de432e2c6b296a947d8a5ee88f68522cf"
|
|
||||||
|
|
||||||
# Must be remove when logrus migration happen
|
# Must be remove when logrus migration happen
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/vulcand/predicate"
|
name = "github.com/vulcand/predicate"
|
||||||
|
|
|
@ -24,6 +24,7 @@ type Account struct {
|
||||||
PrivateKey []byte
|
PrivateKey []byte
|
||||||
DomainsCertificate DomainsCertificates
|
DomainsCertificate DomainsCertificates
|
||||||
ChallengeCerts map[string]*ChallengeCert
|
ChallengeCerts map[string]*ChallengeCert
|
||||||
|
HTTPChallenge map[string]map[string][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChallengeCert stores a challenge certificate
|
// ChallengeCert stores a challenge certificate
|
||||||
|
|
106
acme/acme.go
106
acme/acme.go
|
@ -7,6 +7,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -14,6 +16,8 @@ import (
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/mux"
|
||||||
"github.com/containous/staert"
|
"github.com/containous/staert"
|
||||||
"github.com/containous/traefik/cluster"
|
"github.com/containous/traefik/cluster"
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
|
@ -37,23 +41,37 @@ type ACME struct {
|
||||||
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||||
Storage string `description:"File or key used for certificates storage."`
|
Storage string `description:"File or key used for certificates storage."`
|
||||||
StorageFile string // deprecated
|
StorageFile string // deprecated
|
||||||
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
OnDemand bool `description:"Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated
|
||||||
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||||
CAServer string `description:"CA server to use."`
|
CAServer string `description:"CA server to use."`
|
||||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||||
DNSProvider string `description:"Use a DNS based challenge provider rather than HTTPS."`
|
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge"`
|
||||||
DelayDontCheckDNS int `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
|
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge"`
|
||||||
|
DNSProvider string `description:"Use a DNS-01 acme challenge rather than TLS-SNI-01 challenge."` // deprecated
|
||||||
|
DelayDontCheckDNS flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // deprecated
|
||||||
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
||||||
client *acme.Client
|
client *acme.Client
|
||||||
defaultCertificate *tls.Certificate
|
defaultCertificate *tls.Certificate
|
||||||
store cluster.Store
|
store cluster.Store
|
||||||
challengeProvider *challengeProvider
|
challengeTLSProvider *challengeTLSProvider
|
||||||
|
challengeHTTPProvider *challengeHTTPProvider
|
||||||
checkOnDemandDomain func(domain string) bool
|
checkOnDemandDomain func(domain string) bool
|
||||||
jobs *channels.InfiniteChannel
|
jobs *channels.InfiniteChannel
|
||||||
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
|
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
|
||||||
dynamicCerts *safe.Safe
|
dynamicCerts *safe.Safe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DNSChallenge contains DNS challenge Configuration
|
||||||
|
type DNSChallenge struct {
|
||||||
|
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
|
||||||
|
DelayBeforeCheck flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPChallenge contains HTTP challenge Configuration
|
||||||
|
type HTTPChallenge struct {
|
||||||
|
EntryPoint string `description:"HTTP challenge EntryPoint"`
|
||||||
|
}
|
||||||
|
|
||||||
//Domains parse []Domain
|
//Domains parse []Domain
|
||||||
type Domains []Domain
|
type Domains []Domain
|
||||||
|
|
||||||
|
@ -107,15 +125,39 @@ func (a *ACME) init() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.defaultCertificate = cert
|
a.defaultCertificate = cert
|
||||||
// TODO: to remove in the future
|
|
||||||
if len(a.StorageFile) > 0 && len(a.Storage) == 0 {
|
|
||||||
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
|
||||||
a.Storage = a.StorageFile
|
|
||||||
}
|
|
||||||
a.jobs = channels.NewInfiniteChannel()
|
a.jobs = channels.NewInfiniteChannel()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddRoutes add routes on internal router
|
||||||
|
func (a *ACME) AddRoutes(router *mux.Router) {
|
||||||
|
router.Methods(http.MethodGet).
|
||||||
|
Path(acme.HTTP01ChallengePath("{token}")).
|
||||||
|
Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if a.challengeHTTPProvider == nil {
|
||||||
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
if token, ok := vars["token"]; ok {
|
||||||
|
domain, _, err := net.SplitHostPort(req.Host)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Unable to split host and port: %v. Fallback to request host.", err)
|
||||||
|
domain = req.Host
|
||||||
|
}
|
||||||
|
tokenValue := a.challengeHTTPProvider.getTokenValue(token, domain)
|
||||||
|
if len(tokenValue) > 0 {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
rw.Write(tokenValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
|
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
|
||||||
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, certs *safe.Safe, checkOnDemandDomain func(domain string) bool) error {
|
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, certs *safe.Safe, checkOnDemandDomain func(domain string) bool) error {
|
||||||
err := a.init()
|
err := a.init()
|
||||||
|
@ -155,7 +197,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||||
}
|
}
|
||||||
|
|
||||||
a.store = datastore
|
a.store = datastore
|
||||||
a.challengeProvider = &challengeProvider{store: a.store}
|
a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
|
||||||
|
|
||||||
ticker := time.NewTicker(24 * time.Hour)
|
ticker := time.NewTicker(24 * time.Hour)
|
||||||
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
||||||
|
@ -171,7 +213,11 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
leadership.AddListener(func(elected bool) error {
|
leadership.AddListener(a.leadershipListener)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACME) leadershipListener(elected bool) error {
|
||||||
if elected {
|
if elected {
|
||||||
_, err := a.store.Load()
|
_, err := a.store.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -191,9 +237,6 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||||
}
|
}
|
||||||
needRegister = true
|
needRegister = true
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.client, err = a.buildACMEClient(account)
|
a.client, err = a.buildACMEClient(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -212,6 +255,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||||
log.Debug("AgreeToTOS...")
|
log.Debug("AgreeToTOS...")
|
||||||
err = a.client.AgreeToTOS()
|
err = a.client.AgreeToTOS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
// Let's Encrypt Subscriber Agreement renew ?
|
// Let's Encrypt Subscriber Agreement renew ?
|
||||||
reg, err := a.client.QueryRegistration()
|
reg, err := a.client.QueryRegistration()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -233,8 +277,6 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||||
a.runJobs()
|
a.runJobs()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLocalConfig creates a tls.config using local ACME configuration
|
// CreateLocalConfig creates a tls.config using local ACME configuration
|
||||||
|
@ -253,7 +295,7 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, certs *safe.Safe, checkO
|
||||||
a.TLSConfig = tlsConfig
|
a.TLSConfig = tlsConfig
|
||||||
localStore := NewLocalStore(a.Storage)
|
localStore := NewLocalStore(a.Storage)
|
||||||
a.store = localStore
|
a.store = localStore
|
||||||
a.challengeProvider = &challengeProvider{store: a.store}
|
a.challengeTLSProvider = &challengeTLSProvider{store: a.store}
|
||||||
|
|
||||||
var needRegister bool
|
var needRegister bool
|
||||||
var account *Account
|
var account *Account
|
||||||
|
@ -337,7 +379,7 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
|
||||||
return providedCertificate, nil
|
return providedCertificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
|
if challengeCert, ok := a.challengeTLSProvider.getCertificate(domain); ok {
|
||||||
log.Debugf("ACME got challenge %s", domain)
|
log.Debugf("ACME got challenge %s", domain)
|
||||||
return challengeCert, nil
|
return challengeCert, nil
|
||||||
}
|
}
|
||||||
|
@ -351,7 +393,7 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
|
||||||
}
|
}
|
||||||
return a.loadCertificateOnDemand(clientHello)
|
return a.loadCertificateOnDemand(clientHello)
|
||||||
}
|
}
|
||||||
log.Debugf("ACME got nothing %s", domain)
|
log.Debugf("No certificate found or generated for %s", domain)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,16 +514,16 @@ func (a *ACME) storeRenewedCertificate(certificateResource *DomainsCertificate,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dnsOverrideDelay(delay int) error {
|
func dnsOverrideDelay(delay flaeg.Duration) error {
|
||||||
var err error
|
var err error
|
||||||
if delay > 0 {
|
if delay > 0 {
|
||||||
log.Debugf("Delaying %d seconds rather than validating DNS propagation", delay)
|
log.Debugf("Delaying %d rather than validating DNS propagation", delay)
|
||||||
acme.PreCheckDNS = func(_, _ string) (bool, error) {
|
acme.PreCheckDNS = func(_, _ string) (bool, error) {
|
||||||
time.Sleep(time.Duration(delay) * time.Second)
|
time.Sleep(time.Duration(delay))
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
} else if delay < 0 {
|
} else if delay < 0 {
|
||||||
err = fmt.Errorf("invalid negative DelayDontCheckDNS: %d", delay)
|
err = fmt.Errorf("invalid negative DelayBeforeCheck: %d", delay)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -497,25 +539,29 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(a.DNSProvider) > 0 {
|
if a.DNSChallenge != nil && len(a.DNSChallenge.Provider) > 0 {
|
||||||
log.Debugf("Using DNS Challenge provider: %s", a.DNSProvider)
|
log.Debugf("Using DNS Challenge provider: %s", a.DNSChallenge.Provider)
|
||||||
|
|
||||||
err = dnsOverrideDelay(a.DelayDontCheckDNS)
|
err = dnsOverrideDelay(a.DNSChallenge.DelayBeforeCheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var provider acme.ChallengeProvider
|
var provider acme.ChallengeProvider
|
||||||
provider, err = dns.NewDNSChallengeProviderByName(a.DNSProvider)
|
provider, err = dns.NewDNSChallengeProviderByName(a.DNSChallenge.Provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
|
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
|
||||||
err = client.SetChallengeProvider(acme.DNS01, provider)
|
err = client.SetChallengeProvider(acme.DNS01, provider)
|
||||||
|
} else if a.HTTPChallenge != nil && len(a.HTTPChallenge.EntryPoint) > 0 {
|
||||||
|
client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01})
|
||||||
|
a.challengeHTTPProvider = &challengeHTTPProvider{store: a.store}
|
||||||
|
err = client.SetChallengeProvider(acme.HTTP01, a.challengeHTTPProvider)
|
||||||
} else {
|
} else {
|
||||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
||||||
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
|
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeTLSProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -623,7 +669,7 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||||
// Get provided certificate which check a domains list (Main and SANs)
|
// Get provided certificate which check a domains list (Main and SANs)
|
||||||
// from static and dynamic provided certificates
|
// from static and dynamic provided certificates
|
||||||
func (a *ACME) getProvidedCertificate(domains []string) *tls.Certificate {
|
func (a *ACME) getProvidedCertificate(domains []string) *tls.Certificate {
|
||||||
log.Debugf("Look for provided certificate to validate %s...", domains)
|
log.Debugf("Looking for provided certificate to validate %s...", domains)
|
||||||
cert := searchProvidedCertificateForDomains(domains, a.TLSConfig.NameToCertificate)
|
cert := searchProvidedCertificateForDomains(domains, a.TLSConfig.NameToCertificate)
|
||||||
if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil {
|
if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil {
|
||||||
cert = searchProvidedCertificateForDomains(domains, a.dynamicCerts.Get().(*traefikTls.DomainsCertificates).Get().(map[string]*tls.Certificate))
|
cert = searchProvidedCertificateForDomains(domains, a.dynamicCerts.Get().(*traefikTls.DomainsCertificates).Get().(map[string]*tls.Certificate))
|
||||||
|
@ -659,7 +705,7 @@ func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||||
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
|
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
log.Error(failures)
|
log.Error(failures)
|
||||||
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
return nil, fmt.Errorf("cannot obtain certificates %+v", failures)
|
||||||
}
|
}
|
||||||
log.Debugf("Loaded ACME certificates %s", domains)
|
log.Debugf("Loaded ACME certificates %s", domains)
|
||||||
return &Certificate{
|
return &Certificate{
|
||||||
|
|
43
acme/acme_example.json
Normal file
43
acme/acme_example.json
Normal 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": {}
|
||||||
|
}
|
|
@ -267,7 +267,7 @@ cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`)
|
||||||
}`))
|
}`))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
a := ACME{DNSProvider: "manual", DelayDontCheckDNS: 10, CAServer: ts.URL}
|
a := ACME{DNSChallenge: &DNSChallenge{Provider: "manual", DelayBeforeCheck: 10}, CAServer: ts.URL}
|
||||||
|
|
||||||
client, err := a.buildACMEClient(account)
|
client, err := a.buildACMEClient(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
92
acme/challenge_http_provider.go
Normal file
92
acme/challenge_http_provider.go
Normal 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
|
||||||
|
}
|
|
@ -23,15 +23,15 @@ import (
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
|
var _ acme.ChallengeProviderTimeout = (*challengeTLSProvider)(nil)
|
||||||
|
|
||||||
type challengeProvider struct {
|
type challengeTLSProvider struct {
|
||||||
store cluster.Store
|
store cluster.Store
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
func (c *challengeTLSProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||||
log.Debugf("Challenge GetCertificate %s", domain)
|
log.Debugf("Looking for an existing ACME challenge for %s...", domain)
|
||||||
if !strings.HasSuffix(domain, ".acme.invalid") {
|
if !strings.HasSuffix(domain, ".acme.invalid") {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate
|
||||||
return result, true
|
return result, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *challengeProvider) Present(domain, token, keyAuth string) error {
|
func (c *challengeTLSProvider) Present(domain, token, keyAuth string) error {
|
||||||
log.Debugf("Challenge Present %s", domain)
|
log.Debugf("Challenge Present %s", domain)
|
||||||
cert, _, err := tlsSNI01ChallengeCert(keyAuth)
|
cert, _, err := tlsSNI01ChallengeCert(keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -88,7 +88,7 @@ func (c *challengeProvider) Present(domain, token, keyAuth string) error {
|
||||||
return transaction.Commit(account)
|
return transaction.Commit(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
|
func (c *challengeTLSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
log.Debugf("Challenge CleanUp %s", domain)
|
log.Debugf("Challenge CleanUp %s", domain)
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
@ -101,7 +101,7 @@ func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
return transaction.Commit(account)
|
return transaction.Commit(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *challengeProvider) Timeout() (timeout, interval time.Duration) {
|
func (c *challengeTLSProvider) Timeout() (timeout, interval time.Duration) {
|
||||||
return 60 * time.Second, 5 * time.Second
|
return 60 * time.Second, 5 * time.Second
|
||||||
}
|
}
|
||||||
|
|
41
acme/localStore_test.go
Normal file
41
acme/localStore_test.go
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ type DashboardHandler struct{}
|
||||||
func (g DashboardHandler) AddRoutes(router *mux.Router) {
|
func (g DashboardHandler) AddRoutes(router *mux.Router) {
|
||||||
// Expose dashboard
|
// Expose dashboard
|
||||||
router.Methods(http.MethodGet).Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
router.Methods(http.MethodGet).Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
http.Redirect(response, request, "/dashboard/", 302)
|
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", 302)
|
||||||
})
|
})
|
||||||
router.Methods(http.MethodGet).PathPrefix("/dashboard/").
|
router.Methods(http.MethodGet).PathPrefix("/dashboard/").
|
||||||
Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"})))
|
Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"})))
|
||||||
|
|
|
@ -20,8 +20,8 @@ type Handler struct {
|
||||||
Debug bool `export:"true"`
|
Debug bool `export:"true"`
|
||||||
CurrentConfigurations *safe.Safe
|
CurrentConfigurations *safe.Safe
|
||||||
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
||||||
Stats *thoas_stats.Stats
|
Stats *thoas_stats.Stats `json:"-"`
|
||||||
StatsRecorder *middlewares.StatsRecorder
|
StatsRecorder *middlewares.StatsRecorder `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -169,7 +169,7 @@ func TestDo_globalConfiguration(t *testing.T) {
|
||||||
OnHostRule: true,
|
OnHostRule: true,
|
||||||
CAServer: "CAServer",
|
CAServer: "CAServer",
|
||||||
EntryPoint: "EntryPoint",
|
EntryPoint: "EntryPoint",
|
||||||
DNSProvider: "DNSProvider",
|
DNSChallenge: &acme.DNSChallenge{Provider: "DNSProvider"},
|
||||||
DelayDontCheckDNS: 666,
|
DelayDontCheckDNS: 666,
|
||||||
ACMELogging: true,
|
ACMELogging: true,
|
||||||
TLSConfig: &tls.Config{
|
TLSConfig: &tls.Config{
|
||||||
|
|
|
@ -243,6 +243,23 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
|
||||||
log.Errorln("Error using file configuration backend, no filename defined")
|
log.Errorln("Error using file configuration backend, no filename defined")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if gc.ACME != nil {
|
||||||
|
// TODO: to remove in the futurs
|
||||||
|
if len(gc.ACME.StorageFile) > 0 && len(gc.ACME.Storage) == 0 {
|
||||||
|
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
||||||
|
gc.ACME.Storage = gc.ACME.StorageFile
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gc.ACME.DNSProvider) > 0 {
|
||||||
|
log.Warn("ACME.DNSProvider is deprecated, use ACME.DNSChallenge instead")
|
||||||
|
gc.ACME.DNSChallenge = &acme.DNSChallenge{Provider: gc.ACME.DNSProvider, DelayBeforeCheck: gc.ACME.DelayDontCheckDNS}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gc.ACME.OnDemand {
|
||||||
|
log.Warn("ACME.OnDemand is deprecated")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultEntryPoints holds default entry points
|
// DefaultEntryPoints holds default entry points
|
||||||
|
|
|
@ -632,8 +632,7 @@ Once a day (the first call begins 10 minutes after the start of Træfik), we col
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[Docker]
|
[Docker]
|
||||||
endpoint = "tcp://10.10.10.10:2375"
|
endpoint = "tcp://10.10.10.10:2375"
|
||||||
|
@ -663,8 +662,7 @@ Once a day (the first call begins 10 minutes after the start of Træfik), we col
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[Docker]
|
[Docker]
|
||||||
Endpoint = "xxxx"
|
Endpoint = "xxxx"
|
||||||
|
|
|
@ -7,10 +7,14 @@ See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) an
|
||||||
```toml
|
```toml
|
||||||
# Sample entrypoint configuration when using ACME.
|
# Sample entrypoint configuration when using ACME.
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml
|
||||||
# Enable ACME (Let's Encrypt): automatic SSL.
|
# Enable ACME (Let's Encrypt): automatic SSL.
|
||||||
[acme]
|
[acme]
|
||||||
|
|
||||||
|
@ -33,17 +37,16 @@ email = "test@traefik.io"
|
||||||
storage = "acme.json"
|
storage = "acme.json"
|
||||||
# or `storage = "traefik/acme/account"` if using KV store.
|
# or `storage = "traefik/acme/account"` if using KV store.
|
||||||
|
|
||||||
# Entrypoint to proxy acme challenge/apply certificates to.
|
# Entrypoint to proxy acme apply certificates to.
|
||||||
# WARNING, must point to an entrypoint on port 443
|
# WARNING, if the TLS-SNI-01 challenge is used, it must point to an entrypoint on port 443
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
|
|
||||||
# Use a DNS based acme challenge rather than external HTTPS access
|
# Use a DNS-01 acme challenge rather than TLS-SNI-01 challenge
|
||||||
#
|
#
|
||||||
#
|
# Optional (Deprecated, replaced by [acme.dnsChallenge])
|
||||||
# Optional
|
|
||||||
#
|
#
|
||||||
# dnsProvider = "digitalocean"
|
# dnsProvider = "digitalocean"
|
||||||
|
|
||||||
|
@ -51,25 +54,29 @@ entryPoint = "https"
|
||||||
# If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds.
|
# If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds.
|
||||||
# Useful if internal networks block external DNS queries.
|
# Useful if internal networks block external DNS queries.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional (Deprecated, replaced by [acme.dnsChallenge])
|
||||||
|
# Default: 0
|
||||||
#
|
#
|
||||||
# delayDontCheckDNS = 0
|
# delayDontCheckDNS = 0
|
||||||
|
|
||||||
# If true, display debug log messages from the acme client library.
|
# If true, display debug log messages from the acme client library.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
|
# Default: false
|
||||||
#
|
#
|
||||||
# acmeLogging = true
|
# acmeLogging = true
|
||||||
|
|
||||||
# Enable on demand certificate. (Deprecated)
|
# Enable on demand certificate generation.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional (Deprecated)
|
||||||
|
# Default: false
|
||||||
#
|
#
|
||||||
# onDemand = true
|
# onDemand = true
|
||||||
|
|
||||||
# Enable certificate generation on frontends Host rules.
|
# Enable certificate generation on frontends Host rules.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
|
# Default: false
|
||||||
#
|
#
|
||||||
# onHostRule = true
|
# onHostRule = true
|
||||||
|
|
||||||
|
@ -78,6 +85,7 @@ entryPoint = "https"
|
||||||
# - Leave comment to go to prod.
|
# - Leave comment to go to prod.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
|
# Default: "https://acme-v01.api.letsencrypt.org/directory"
|
||||||
#
|
#
|
||||||
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
|
@ -93,11 +101,48 @@ entryPoint = "https"
|
||||||
# main = "local3.com"
|
# main = "local3.com"
|
||||||
# [[acme.domains]]
|
# [[acme.domains]]
|
||||||
# main = "local4.com"
|
# main = "local4.com"
|
||||||
|
|
||||||
|
# Use a HTTP-01 acme challenge rather than TLS-SNI-01 challenge
|
||||||
|
#
|
||||||
|
# Optional but recommend
|
||||||
|
#
|
||||||
|
[acme.httpChallenge]
|
||||||
|
|
||||||
|
# EntryPoint to use for the challenges.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
entryPoint = "http"
|
||||||
|
|
||||||
|
# Use a DNS-01 acme challenge rather than TLS-SNI-01 challenge
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [acme.dnsChallenge]
|
||||||
|
|
||||||
|
# Provider used.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
# provider = "digitalocean"
|
||||||
|
|
||||||
|
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
|
||||||
|
# If delayBeforeCheck is greater than zero, avoid this & instead just wait so many seconds.
|
||||||
|
# Useful if internal networks block external DNS queries.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 0
|
||||||
|
#
|
||||||
|
# delayBeforeCheck = 0
|
||||||
```
|
```
|
||||||
|
!!! note
|
||||||
|
Even if `TLS-SNI-01` challenge is [disabled](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188) for the moment, it stays the _by default_ ACME Challenge in Træfik.
|
||||||
|
If `TLS-SNI-01` challenge is not re-enabled in the future, it we will be removed from Træfik.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
ACME entryPoint has to be relied to the port 443, otherwise ACME Challenges can not be done.
|
If `TLS-SNI-01` challenge is used, `acme.entryPoint` has to be reachable by Let's Encrypt through the port 443.
|
||||||
It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
If `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through the port 80.
|
||||||
|
These are Let's Encrypt limitations as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||||
|
|
||||||
### `storage`
|
### `storage`
|
||||||
|
|
||||||
|
@ -110,7 +155,7 @@ storage = "acme.json"
|
||||||
|
|
||||||
File or key used for certificates storage.
|
File or key used for certificates storage.
|
||||||
|
|
||||||
**WARNING** If you use Træfik in Docker, you have 2 options:
|
**WARNING:** If you use Træfik in Docker, you have 2 options:
|
||||||
|
|
||||||
- create a file on your host and mount it as a volume:
|
- create a file on your host and mount it as a volume:
|
||||||
```toml
|
```toml
|
||||||
|
@ -133,19 +178,60 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
During Træfik configuration migration from a configuration file to a KV store (thanks to `storeconfig` subcommand as described [here](/user-guide/kv-config/#store-configuration-in-key-value-store)), if ACME certificates have to be migrated too, use both `storageFile` and `storage`.
|
During Træfik configuration migration from a configuration file to a KV store (thanks to `storeconfig` subcommand as described [here](/user-guide/kv-config/#store-configuration-in-key-value-store)), if ACME certificates have to be migrated too, use both `storageFile` and `storage`.
|
||||||
`storageFile` will contain the path to the `acme.json` file to migrate.
|
|
||||||
`storage` will contain the key where the certificates will be stored.
|
|
||||||
|
|
||||||
### `dnsProvider`
|
- `storageFile` will contain the path to the `acme.json` file to migrate.
|
||||||
|
- `storage` will contain the key where the certificates will be stored.
|
||||||
|
|
||||||
|
### `acme.httpChallenge`
|
||||||
|
|
||||||
|
Use `HTTP-01` challenge to generate/renew ACME certificates.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[acme]
|
[acme]
|
||||||
# ...
|
# ...
|
||||||
dnsProvider = "digitalocean"
|
entryPoint = "https"
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `entryPoint`
|
||||||
|
|
||||||
|
Specify the entryPoint to use during the challenges.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
entryPoint = "https"
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
`acme.httpChallenge.entryPoint` has to be reachable by Let's Encrypt through the port 80.
|
||||||
|
It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||||
|
|
||||||
|
### `acme.dnsChallenge`
|
||||||
|
|
||||||
|
Use `DNS-01` challenge to generate/renew ACME certificates.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
[acme.dnsChallenge]
|
||||||
|
provider = "digitalocean"
|
||||||
|
delayBeforeCheck = 0
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Use a DNS based acme challenge rather than external HTTPS access, e.g. for a firewalled server.
|
#### `provider`
|
||||||
|
|
||||||
Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables to enable setting it:
|
Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables to enable setting it:
|
||||||
|
|
||||||
|
@ -175,22 +261,21 @@ Select the provider that matches the DNS domain that will host the challenge TXT
|
||||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or configured user/instance IAM profile. |
|
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or configured user/instance IAM profile. |
|
||||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` |
|
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` |
|
||||||
|
|
||||||
### `delayDontCheckDNS`
|
#### `delayBeforeCheck`
|
||||||
|
|
||||||
```toml
|
By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
|
||||||
[acme]
|
If `delayBeforeCheck` is greater than zero, avoid this & instead just wait so many seconds.
|
||||||
# ...
|
|
||||||
delayDontCheckDNS = 0
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify.
|
|
||||||
If `delayDontCheckDNS` is greater than zero, avoid this & instead just wait so many seconds.
|
|
||||||
|
|
||||||
Useful if internal networks block external DNS queries.
|
Useful if internal networks block external DNS queries.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This field has no sense if a `provider` is not defined.
|
||||||
|
|
||||||
### `onDemand` (Deprecated)
|
### `onDemand` (Deprecated)
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
This option is deprecated.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[acme]
|
[acme]
|
||||||
# ...
|
# ...
|
||||||
|
@ -208,9 +293,6 @@ This will request a certificate from Let's Encrypt during the first TLS handshak
|
||||||
!!! warning
|
!!! warning
|
||||||
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||||
|
|
||||||
!!! warning
|
|
||||||
This option is deprecated.
|
|
||||||
|
|
||||||
### `onHostRule`
|
### `onHostRule`
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -240,21 +322,21 @@ CA server to use.
|
||||||
- Uncomment the line to run on the staging Let's Encrypt server.
|
- Uncomment the line to run on the staging Let's Encrypt server.
|
||||||
- Leave comment to go to prod.
|
- Leave comment to go to prod.
|
||||||
|
|
||||||
### `domains`
|
### `acme.domains`
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[acme]
|
[acme]
|
||||||
# ...
|
# ...
|
||||||
[[acme.domains]]
|
[[acme.domains]]
|
||||||
main = "local1.com"
|
main = "local1.com"
|
||||||
sans = ["test1.local1.com", "test2.local1.com"]
|
sans = ["test1.local1.com", "test2.local1.com"]
|
||||||
[[acme.domains]]
|
[[acme.domains]]
|
||||||
main = "local2.com"
|
main = "local2.com"
|
||||||
sans = ["test1.local2.com", "test2.local2.com"]
|
sans = ["test1.local2.com", "test2.local2.com"]
|
||||||
[[acme.domains]]
|
[[acme.domains]]
|
||||||
main = "local3.com"
|
main = "local3.com"
|
||||||
[[acme.domains]]
|
[[acme.domains]]
|
||||||
main = "local4.com"
|
main = "local4.com"
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -265,3 +347,15 @@ All domains must have A/AAAA records pointing to Træfik.
|
||||||
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||||
|
|
||||||
Each domain & SANs will lead to a certificate request.
|
Each domain & SANs will lead to a certificate request.
|
||||||
|
|
||||||
|
### `dnsProvider` (Deprecated)
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
This option is deprecated.
|
||||||
|
Please refer to [DNS challenge provider section](/configuration/acme/#provider)
|
||||||
|
|
||||||
|
### `delayDontCheckDNS` (Deprecated)
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
This option is deprecated.
|
||||||
|
Please refer to [DNS challenge delayBeforeCheck section](/configuration/acme/#delaybeforecheck)
|
||||||
|
|
|
@ -161,7 +161,7 @@ curl -s "http://localhost:8080/health" | jq .
|
||||||
// average response time in seconds
|
// average response time in seconds
|
||||||
"average_response_time_sec": 0.8648016000000001,
|
"average_response_time_sec": 0.8648016000000001,
|
||||||
|
|
||||||
// request statistics [requires --web.statistics to be set]
|
// request statistics [requires --statistics to be set]
|
||||||
// ten most recent requests with 4xx and 5xx status codes
|
// ten most recent requests with 4xx and 5xx status codes
|
||||||
"recent_errors": [
|
"recent_errors": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,6 @@ Træfik can be configured to use Kubernetes Ingress as a backend configuration.
|
||||||
|
|
||||||
See also [Kubernetes user guide](/user-guide/kubernetes).
|
See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -44,7 +43,7 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||||
#
|
#
|
||||||
# namespaces = ["default", "production"]
|
# namespaces = ["default", "production"]
|
||||||
|
|
||||||
# Ingress label selector to identify Ingress objects that should be processed.
|
# Ingress label selector to filter Ingress objects that should be processed.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
# Default: empty (process all Ingresses)
|
# Default: empty (process all Ingresses)
|
||||||
|
@ -75,28 +74,31 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||||
|
|
||||||
### `endpoint`
|
### `endpoint`
|
||||||
|
|
||||||
The Kubernetes server endpoint.
|
The Kubernetes server endpoint as URL.
|
||||||
|
|
||||||
When deployed as a replication controller in Kubernetes, Traefik will use the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` to construct the endpoint.
|
When deployed into Kubernetes, Traefik will read the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` to construct the endpoint.
|
||||||
|
|
||||||
Secure token will be found in `/var/run/secrets/kubernetes.io/serviceaccount/token` and SSL CA cert in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`
|
The access token will be looked up in `/var/run/secrets/kubernetes.io/serviceaccount/token` and the SSL CA certificate in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`.
|
||||||
|
Both are provided mounted automatically when deployed inside Kubernetes.
|
||||||
|
|
||||||
The endpoint may be given to override the environment variable values.
|
The endpoint may be specified to override the environment variable values inside a cluster.
|
||||||
|
|
||||||
When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client.
|
When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client.
|
||||||
In this case, the endpoint is required.
|
In this case, the endpoint is required.
|
||||||
Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster from localhost.
|
Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster using the granted autentication and authorization of the associated kubeconfig.
|
||||||
|
|
||||||
### `labelselector`
|
### `labelselector`
|
||||||
|
|
||||||
Ingress label selector to identify Ingress objects that should be processed.
|
By default, Traefik processes all Ingress objects in the configured namespaces.
|
||||||
|
A label selector can be defined to filter on specific Ingress objects only.
|
||||||
|
|
||||||
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details.
|
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details.
|
||||||
|
|
||||||
|
|
||||||
## Annotations
|
## Annotations
|
||||||
|
|
||||||
Annotations can be used on containers to override default behaviour for the whole Ingress resource:
|
### General annotations
|
||||||
|
|
||||||
|
The following general annotations are applicable on the Ingress object:
|
||||||
|
|
||||||
- `traefik.frontend.rule.type: PathPrefixStrip`
|
- `traefik.frontend.rule.type: PathPrefixStrip`
|
||||||
Override the default frontend rule type. Default: `PathPrefix`.
|
Override the default frontend rule type. Default: `PathPrefix`.
|
||||||
|
@ -114,44 +116,35 @@ Annotations can be used on containers to override default behaviour for the whol
|
||||||
Override the default frontend PassTLSCert value. Default: `false`.
|
Override the default frontend PassTLSCert value. Default: `false`.
|
||||||
- `ingress.kubernetes.io/rewrite-target: /users`
|
- `ingress.kubernetes.io/rewrite-target: /users`
|
||||||
Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header.
|
Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header.
|
||||||
|
- `ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"`
|
||||||
|
A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Please note that `traefik.frontend.redirect.regex` and `traefik.frontend.redirect.replacement` do not have to be set if `traefik.frontend.redirect.entryPoint` is defined for the redirection (they will not be used in this case).
|
Please note that `traefik.frontend.redirect.regex` and `traefik.frontend.redirect.replacement` do not have to be set if `traefik.frontend.redirect.entryPoint` is defined for the redirection (they will not be used in this case).
|
||||||
|
|
||||||
|
The following annotations are applicable on the Service object associated with a particular Ingress object:
|
||||||
Annotations can be used on the Kubernetes service to override default behaviour:
|
|
||||||
|
|
||||||
- `traefik.backend.loadbalancer.method=drr`
|
- `traefik.backend.loadbalancer.method=drr`
|
||||||
Override the default `wrr` load balancer algorithm
|
Override the default `wrr` load balancer algorithm.
|
||||||
- `traefik.backend.loadbalancer.stickiness=true`
|
- `traefik.backend.loadbalancer.stickiness=true`
|
||||||
Enable backend sticky sessions
|
Enable backend sticky sessions.
|
||||||
- `traefik.backend.loadbalancer.stickiness.cookieName=NAME`
|
- `traefik.backend.loadbalancer.stickiness.cookieName=NAME`
|
||||||
Manually set the cookie name for sticky sessions
|
Manually set the cookie name for sticky sessions.
|
||||||
- `traefik.backend.loadbalancer.sticky=true`
|
- `traefik.backend.loadbalancer.sticky=true`
|
||||||
Enable backend sticky sessions (DEPRECATED)
|
Enable backend sticky sessions (DEPRECATED).
|
||||||
|
|
||||||
Additionally, an annotation can be used on Kubernetes services to set the [circuit breaker expression](/basics/#backends) for a backend.
|
|
||||||
|
|
||||||
- `traefik.backend.circuitbreaker: <expression>`
|
- `traefik.backend.circuitbreaker: <expression>`
|
||||||
Set the circuit breaker expression for the backend. Default: `nil`.
|
Set the circuit breaker expression for the backend.
|
||||||
|
|
||||||
As known from nginx when used as Kubernetes Ingress Controller, a list of IP-Ranges which are allowed to access can be configured by using an ingress annotation:
|
### Security annotations
|
||||||
|
|
||||||
- `ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"`
|
The following security annotations are applicable on the Ingress object:
|
||||||
|
|
||||||
An unset or empty list allows all Source-IPs to access.
|
|
||||||
If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access.
|
|
||||||
|
|
||||||
#### Security annotations
|
|
||||||
|
|
||||||
The following security annotations can be applied to the ingress object to add security features:
|
|
||||||
|
|
||||||
| Annotation | Description |
|
| Annotation | Description |
|
||||||
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `ingress.kubernetes.io/allowed-hosts:EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` |
|
| `ingress.kubernetes.io/allowed-hosts:EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` |
|
||||||
| `ingress.kubernetes.io/custom-request-headers:EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: <code>HEADER:value||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||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||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||HEADER2:value2</code> |
|
||||||
| `ingress.kubernetes.io/proxy-headers:EXPR ` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` |
|
| `ingress.kubernetes.io/proxy-headers:EXPR` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` |
|
||||||
| `ingress.kubernetes.io/ssl-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
| `ingress.kubernetes.io/ssl-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
|
||||||
| `ingress.kubernetes.io/ssl-temporary-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
| `ingress.kubernetes.io/ssl-temporary-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
|
||||||
| `ingress.kubernetes.io/ssl-host:HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
| `ingress.kubernetes.io/ssl-host:HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
|
||||||
|
@ -171,17 +164,17 @@ The following security annotations can be applied to the ingress object to add s
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
Is possible to add additional authentication annotations in the Ingress rule.
|
Is possible to add additional authentication annotations to the Ingress object.
|
||||||
The source of the authentication is a secret that contains usernames and passwords inside the key auth.
|
The source of the authentication is a Secret object that contains the credentials.
|
||||||
|
|
||||||
- `ingress.kubernetes.io/auth-type`: `basic`
|
- `ingress.kubernetes.io/auth-type`: `basic`
|
||||||
|
Contains the authentication type. The only permitted type is `basic`.
|
||||||
- `ingress.kubernetes.io/auth-secret`: `mysecret`
|
- `ingress.kubernetes.io/auth-secret`: `mysecret`
|
||||||
Contains the usernames and passwords with access to the paths defined in the Ingress Rule.
|
Contains the username and password with access to the paths defined in the Ingress object.
|
||||||
|
|
||||||
The secret must be created in the same namespace as the Ingress rule.
|
The secret must be created in the same namespace as the Ingress object.
|
||||||
|
|
||||||
Limitations:
|
The following limitations hold:
|
||||||
|
|
||||||
- Basic authentication only.
|
- The realm is not configurable; the only supported (and default) value is `traefik`.
|
||||||
- Realm not configurable; only `traefik` default.
|
- The Secret must contain a single file only.
|
||||||
- Secret must contain only single file.
|
|
||||||
|
|
|
@ -35,6 +35,15 @@ address = ":8080"
|
||||||
# Default: false
|
# Default: false
|
||||||
#
|
#
|
||||||
readOnly = true
|
readOnly = true
|
||||||
|
|
||||||
|
|
||||||
|
# Set the root path for webui and API
|
||||||
|
#
|
||||||
|
# Deprecated
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# path = "/mypath"
|
||||||
|
#
|
||||||
```
|
```
|
||||||
|
|
||||||
## Web UI
|
## Web UI
|
||||||
|
@ -80,7 +89,7 @@ Users can be specified directly in the toml file, or indirectly by referencing a
|
||||||
|
|
||||||
# To enable digest auth on the webui with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
# To enable digest auth on the webui with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||||
[web.auth.digest]
|
[web.auth.digest]
|
||||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||||
usersFile = "/path/to/.htdigest"
|
usersFile = "/path/to/.htdigest"
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
|
@ -375,3 +384,35 @@ curl -s "http://localhost:8080/api" | jq .
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Path
|
||||||
|
|
||||||
|
As web is deprecated, you can handle the `Path` option like this
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entrypoints.http]
|
||||||
|
address=":80"
|
||||||
|
|
||||||
|
[entrypoints.dashboard]
|
||||||
|
address=":8080"
|
||||||
|
|
||||||
|
[entrypoints.api]
|
||||||
|
address=":8081"
|
||||||
|
|
||||||
|
#Activate API and Dashboard
|
||||||
|
[api]
|
||||||
|
entrypoint="api"
|
||||||
|
|
||||||
|
[file]
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.servers.server1]
|
||||||
|
url = "http://127.0.0.1:8081"
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend1]
|
||||||
|
entrypoints=["dashboard"]
|
||||||
|
backend = "backend1"
|
||||||
|
[frontends.frontend1.routes.test_1]
|
||||||
|
rule = "PathPrefixStrip:/yourprefix;PathPrefix:/yourprefix"
|
||||||
|
```
|
|
@ -135,8 +135,8 @@ Users can be specified directly in the toml file, or indirectly by referencing a
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
[entryPoints.http.auth.basic]
|
[entryPoints.http.auth.digest]
|
||||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||||
usersFile = "/path/to/.htdigest"
|
usersFile = "/path/to/.htdigest"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ version: '2'
|
||||||
services:
|
services:
|
||||||
proxy:
|
proxy:
|
||||||
image: traefik
|
image: traefik
|
||||||
command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
|
command: --api --docker --docker.domain=docker.localhost --logLevel=DEBUG
|
||||||
networks:
|
networks:
|
||||||
- webgateway
|
- webgateway
|
||||||
ports:
|
ports:
|
||||||
|
|
|
@ -56,15 +56,18 @@ $ traefik \
|
||||||
--acme \
|
--acme \
|
||||||
--acme.storage=/etc/traefik/acme/acme.json \
|
--acme.storage=/etc/traefik/acme/acme.json \
|
||||||
--acme.entryPoint=https \
|
--acme.entryPoint=https \
|
||||||
|
--acme.httpChallenge.entryPoint=http \
|
||||||
--acme.email=contact@mydomain.ca
|
--acme.email=contact@mydomain.ca
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's Encrypt needs 3 parameters: an entry point to listen to, a storage for certificates, and an email for the registration.
|
Let's Encrypt needs 4 parameters: an TLS entry point to listen to, a non-TLS entry point to allow HTTP challenges, a storage for certificates, and an email for the registration.
|
||||||
|
|
||||||
To enable Let's Encrypt support, you need to add `--acme` flag.
|
To enable Let's Encrypt support, you need to add `--acme` flag.
|
||||||
|
|
||||||
Now, Træfik needs to know where to store the certificates, we can choose between a key in a Key-Value store, or a file path: `--acme.storage=my/key` or `--acme.storage=/path/to/acme.json`.
|
Now, Træfik needs to know where to store the certificates, we can choose between a key in a Key-Value store, or a file path: `--acme.storage=my/key` or `--acme.storage=/path/to/acme.json`.
|
||||||
|
|
||||||
|
The `acme.httpChallenge.entryPoint` flag enables the `HTTP-01` challenge and specifies the entryPoint to use during the challenges.
|
||||||
|
|
||||||
For your email and the entry point, it's `--acme.entryPoint` and `--acme.email` flags.
|
For your email and the entry point, it's `--acme.entryPoint` and `--acme.email` flags.
|
||||||
|
|
||||||
### Docker configuration
|
### Docker configuration
|
||||||
|
@ -90,13 +93,14 @@ services:
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:1.5
|
image: traefik:1.5
|
||||||
command:
|
command:
|
||||||
- "--web"
|
- "--api"
|
||||||
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
||||||
- "--entrypoints=Name:https Address::443 TLS"
|
- "--entrypoints=Name:https Address::443 TLS"
|
||||||
- "--defaultentrypoints=http,https"
|
- "--defaultentrypoints=http,https"
|
||||||
- "--acme"
|
- "--acme"
|
||||||
- "--acme.storage=/etc/traefik/acme/acme.json"
|
- "--acme.storage=/etc/traefik/acme/acme.json"
|
||||||
- "--acme.entryPoint=https"
|
- "--acme.entryPoint=https"
|
||||||
|
- "--acme.httpChallenge.entryPoint=http"
|
||||||
- "--acme.OnHostRule=true"
|
- "--acme.OnHostRule=true"
|
||||||
- "--acme.onDemand=false"
|
- "--acme.onDemand=false"
|
||||||
- "--acme.email=contact@mydomain.ca"
|
- "--acme.email=contact@mydomain.ca"
|
||||||
|
@ -155,7 +159,7 @@ The initializer in a docker-compose file will be:
|
||||||
image: traefik:1.5
|
image: traefik:1.5
|
||||||
command:
|
command:
|
||||||
- "storeconfig"
|
- "storeconfig"
|
||||||
- "--web"
|
- "--api"
|
||||||
[...]
|
[...]
|
||||||
- "--consul"
|
- "--consul"
|
||||||
- "--consul.endpoint=consul:8500"
|
- "--consul.endpoint=consul:8500"
|
||||||
|
@ -199,19 +203,20 @@ services:
|
||||||
image: traefik:1.5
|
image: traefik:1.5
|
||||||
command:
|
command:
|
||||||
- "storeconfig"
|
- "storeconfig"
|
||||||
- "--web"
|
- "--api"
|
||||||
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
||||||
- "--entrypoints=Name:https Address::443 TLS"
|
- "--entrypoints=Name:https Address::443 TLS"
|
||||||
- "--defaultentrypoints=http,https"
|
- "--defaultentrypoints=http,https"
|
||||||
- "--acme"
|
- "--acme"
|
||||||
- "--acme.storage=traefik/acme/account"
|
- "--acme.storage=traefik/acme/account"
|
||||||
- "--acme.entryPoint=https"
|
- "--acme.entryPoint=https"
|
||||||
|
- "--acme.httpChallenge.entryPoint=http"
|
||||||
- "--acme.OnHostRule=true"
|
- "--acme.OnHostRule=true"
|
||||||
- "--acme.onDemand=false"
|
- "--acme.onDemand=false"
|
||||||
- "--acme.email=contact@jmaitrehenry.ca"
|
- "--acme.email=foobar@example.com"
|
||||||
- "--docker"
|
- "--docker"
|
||||||
- "--docker.swarmmode"
|
- "--docker.swarmmode"
|
||||||
- "--docker.domain=jmaitrehenry.ca"
|
- "--docker.domain=example.com"
|
||||||
- "--docker.watch"
|
- "--docker.watch"
|
||||||
- "--consul"
|
- "--consul"
|
||||||
- "--consul.endpoint=consul:8500"
|
- "--consul.endpoint=consul:8500"
|
||||||
|
|
|
@ -104,6 +104,8 @@ email = "your-email-here@my-awesome-app.org"
|
||||||
storage = "acme.json"
|
storage = "acme.json"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
OnHostRule = true
|
OnHostRule = true
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
```
|
```
|
||||||
|
|
||||||
This is the minimum configuration required to do the following:
|
This is the minimum configuration required to do the following:
|
||||||
|
|
|
@ -6,6 +6,7 @@ You will find here some configuration examples of Træfik.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
defaultEntryPoints = ["http"]
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
@ -15,6 +16,7 @@ defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
defaultEntryPoints = ["http", "https"]
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
@ -34,6 +36,7 @@ Note that we can either give path to certificate file or directly the file conte
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
defaultEntryPoints = ["http", "https"]
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
@ -52,10 +55,16 @@ defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
## Let's Encrypt support
|
## Let's Encrypt support
|
||||||
|
|
||||||
### Basic example
|
!!! note
|
||||||
|
Even if `TLS-SNI-01` challenge is [disabled](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188), for the moment, it stays the _by default_ ACME Challenge in Træfik but all the examples use the `HTTP-01` challenge (except DNS challenge examples).
|
||||||
|
If `TLS-SNI-01` challenge is not re-enabled in the future, it we will be removed from Træfik.
|
||||||
|
|
||||||
|
### Basic example with HTTP challenge
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
|
@ -65,6 +74,8 @@ email = "test@traefik.io"
|
||||||
storage = "acme.json"
|
storage = "acme.json"
|
||||||
caServer = "http://172.18.0.1:4000/directory"
|
caServer = "http://172.18.0.1:4000/directory"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
|
|
||||||
[[acme.domains]]
|
[[acme.domains]]
|
||||||
main = "local1.com"
|
main = "local1.com"
|
||||||
|
@ -78,14 +89,16 @@ entryPoint = "https"
|
||||||
main = "local4.com"
|
main = "local4.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com` with described SANs.
|
This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com` with described SANs.
|
||||||
|
|
||||||
Traefik generates these certificates when it starts and it needs to be restart if new domains are added.
|
Traefik generates these certificates when it starts and it needs to be restart if new domains are added.
|
||||||
|
|
||||||
### OnHostRule option
|
### OnHostRule option (with HTTP challenge)
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
|
@ -96,6 +109,8 @@ storage = "acme.json"
|
||||||
onHostRule = true
|
onHostRule = true
|
||||||
caServer = "http://172.18.0.1:4000/directory"
|
caServer = "http://172.18.0.1:4000/directory"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
|
|
||||||
[[acme.domains]]
|
[[acme.domains]]
|
||||||
main = "local1.com"
|
main = "local1.com"
|
||||||
|
@ -109,16 +124,18 @@ entryPoint = "https"
|
||||||
main = "local4.com"
|
main = "local4.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com`.
|
This configuration allows generating Let's Encrypt certificates (thanks to `HTTP-01` challenge) for the four domains `local[1-4].com`.
|
||||||
|
|
||||||
Traefik generates these certificates when it starts.
|
Traefik generates these certificates when it starts.
|
||||||
|
|
||||||
If a backend is added with a `onHost` rule, Traefik will automatically generate the Let's Encrypt certificate for the new domain.
|
If a backend is added with a `onHost` rule, Traefik will automatically generate the Let's Encrypt certificate for the new domain.
|
||||||
|
|
||||||
### OnDemand option
|
### OnDemand option (with HTTP challenge)
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
|
@ -129,9 +146,11 @@ storage = "acme.json"
|
||||||
onDemand = true
|
onDemand = true
|
||||||
caServer = "http://172.18.0.1:4000/directory"
|
caServer = "http://172.18.0.1:4000/directory"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
```
|
```
|
||||||
|
|
||||||
This configuration allows generating a Let's Encrypt certificate during the first HTTPS request on a new domain.
|
This configuration allows generating a Let's Encrypt certificate (thanks to `HTTP-01` challenge) during the first HTTPS request on a new domain.
|
||||||
|
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
@ -153,10 +172,11 @@ This configuration allows generating a Let's Encrypt certificate during the firs
|
||||||
[acme]
|
[acme]
|
||||||
email = "test@traefik.io"
|
email = "test@traefik.io"
|
||||||
storage = "acme.json"
|
storage = "acme.json"
|
||||||
dnsProvider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
|
|
||||||
delayDontCheckDNS = 0
|
|
||||||
caServer = "http://172.18.0.1:4000/directory"
|
caServer = "http://172.18.0.1:4000/directory"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
|
[acme.dnsChallenge]
|
||||||
|
provider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
|
||||||
|
delayBeforeCheck = 0
|
||||||
|
|
||||||
[[acme.domains]]
|
[[acme.domains]]
|
||||||
main = "local1.com"
|
main = "local1.com"
|
||||||
|
@ -173,12 +193,14 @@ entryPoint = "https"
|
||||||
DNS challenge needs environment variables to be executed.
|
DNS challenge needs environment variables to be executed.
|
||||||
This variables have to be set on the machine/container which host Traefik.
|
This variables have to be set on the machine/container which host Traefik.
|
||||||
|
|
||||||
These variables are described [in this section](/configuration/acme/#dnsprovider).
|
These variables are described [in this section](/configuration/acme/#provider).
|
||||||
|
|
||||||
### OnHostRule option and provided certificates
|
### OnHostRule option and provided certificates (with HTTP challenge)
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
|
@ -192,10 +214,11 @@ storage = "acme.json"
|
||||||
onHostRule = true
|
onHostRule = true
|
||||||
caServer = "http://172.18.0.1:4000/directory"
|
caServer = "http://172.18.0.1:4000/directory"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
```
|
```
|
||||||
|
|
||||||
Traefik will only try to generate a Let's encrypt certificate if the domain cannot be checked by the provided certificates.
|
Traefik will only try to generate a Let's encrypt certificate (thanks to `HTTP-01` challenge) if the domain cannot be checked by the provided certificates.
|
||||||
|
|
||||||
### Cluster mode
|
### Cluster mode
|
||||||
|
|
||||||
|
@ -207,6 +230,8 @@ Before you use Let's Encrypt in a Traefik cluster, take a look to [the key-value
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
|
@ -217,6 +242,9 @@ storage = "traefik/acme/account"
|
||||||
caServer = "http://172.18.0.1:4000/directory"
|
caServer = "http://172.18.0.1:4000/directory"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
|
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
|
|
||||||
[[acme.domains]]
|
[[acme.domains]]
|
||||||
main = "local1.com"
|
main = "local1.com"
|
||||||
sans = ["test1.local1.com", "test2.local1.com"]
|
sans = ["test1.local1.com", "test2.local1.com"]
|
||||||
|
@ -244,10 +272,12 @@ The `consul` provider contains the configuration.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[frontends]
|
[frontends]
|
||||||
|
|
||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
[frontends.frontend1.routes.test_1]
|
[frontends.frontend1.routes.test_1]
|
||||||
rule = "Host:test.localhost"
|
rule = "Host:test.localhost"
|
||||||
|
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
|
@ -255,6 +285,7 @@ The `consul` provider contains the configuration.
|
||||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
[frontends.frontend2.routes.test_1]
|
[frontends.frontend2.routes.test_1]
|
||||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||||
|
|
||||||
[frontends.frontend3]
|
[frontends.frontend3]
|
||||||
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
|
@ -272,6 +303,7 @@ Passwords are encoded in MD5: you can use htpasswd to generate those ones.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
defaultEntryPoints = ["http"]
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
@ -286,6 +318,7 @@ via a configurable header value.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
defaultEntryPoints = ["http"]
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
@ -306,7 +339,7 @@ idleTimeout = "360s"
|
||||||
|
|
||||||
## Securing Ping Health Check
|
## Securing Ping Health Check
|
||||||
|
|
||||||
The `/ping` health-check URL is enabled together with the web admin panel, enabled with the command-line `--web` or config file option `[web]`.
|
The `/ping` health-check URL is enabled with the command-line `--ping` or config file option `[ping]`.
|
||||||
Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows:
|
Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows:
|
||||||
|
|
||||||
* Regular path: `http://hostname:80/foo`
|
* Regular path: `http://hostname:80/foo`
|
||||||
|
|
|
@ -57,8 +57,7 @@ RootCAs = [ "./backend.cert" ]
|
||||||
keyFile = "./frontend.key"
|
keyFile = "./frontend.key"
|
||||||
|
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Kubernetes Ingress Controller
|
# Kubernetes Ingress Controller
|
||||||
|
|
||||||
This guide explains how to use Træfik as an Ingress controller in a Kubernetes cluster.
|
This guide explains how to use Træfik as an Ingress controller for a Kubernetes cluster.
|
||||||
|
|
||||||
If you are not familiar with Ingresses in Kubernetes you might want to read the [Kubernetes user guide](https://kubernetes.io/docs/concepts/services-networking/ingress/)
|
If you are not familiar with Ingresses in Kubernetes you might want to read the [Kubernetes user guide](https://kubernetes.io/docs/concepts/services-networking/ingress/)
|
||||||
|
|
||||||
|
@ -8,8 +8,10 @@ The config files used in this guide can be found in the [examples directory](htt
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
1. A working Kubernetes cluster. If you want to follow along with this guide, you should setup [minikube](https://kubernetes.io/docs/getting-started-guides/minikube/)
|
1. A working Kubernetes cluster. If you want to follow along with this guide, you should setup [minikube](https://kubernetes.io/docs/getting-started-guides/minikube/) on your machine, as it is the quickest way to get a local Kubernetes cluster setup for experimentation and development.
|
||||||
on your machine, as it is the quickest way to get a local Kubernetes cluster setup for experimentation and development.
|
|
||||||
|
!!! note
|
||||||
|
The guide is likely not fully adequate for a production-ready setup.
|
||||||
|
|
||||||
2. The `kubectl` binary should be [installed on your workstation](https://kubernetes.io/docs/getting-started-guides/minikube/#download-kubectl).
|
2. The `kubectl` binary should be [installed on your workstation](https://kubernetes.io/docs/getting-started-guides/minikube/#download-kubectl).
|
||||||
|
|
||||||
|
@ -117,7 +119,7 @@ spec:
|
||||||
- image: traefik
|
- image: traefik
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
args:
|
args:
|
||||||
- --web
|
- --api
|
||||||
- --kubernetes
|
- --kubernetes
|
||||||
---
|
---
|
||||||
kind: Service
|
kind: Service
|
||||||
|
@ -137,6 +139,7 @@ spec:
|
||||||
name: admin
|
name: admin
|
||||||
type: NodePort
|
type: NodePort
|
||||||
```
|
```
|
||||||
|
|
||||||
[examples/k8s/traefik-deployment.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-deployment.yaml)
|
[examples/k8s/traefik-deployment.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-deployment.yaml)
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
@ -182,7 +185,7 @@ spec:
|
||||||
privileged: true
|
privileged: true
|
||||||
args:
|
args:
|
||||||
- -d
|
- -d
|
||||||
- --web
|
- --api
|
||||||
- --kubernetes
|
- --kubernetes
|
||||||
---
|
---
|
||||||
kind: Service
|
kind: Service
|
||||||
|
@ -233,7 +236,7 @@ Start by listing the pods in the `kube-system` namespace:
|
||||||
kubectl --namespace=kube-system get pods
|
kubectl --namespace=kube-system get pods
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```shell
|
||||||
NAME READY STATUS RESTARTS AGE
|
NAME READY STATUS RESTARTS AGE
|
||||||
kube-addon-manager-minikubevm 1/1 Running 0 4h
|
kube-addon-manager-minikubevm 1/1 Running 0 4h
|
||||||
kubernetes-dashboard-s8krj 1/1 Running 0 4h
|
kubernetes-dashboard-s8krj 1/1 Running 0 4h
|
||||||
|
@ -250,19 +253,21 @@ _It might take a few moments for kubernetes to pull the Træfik image and start
|
||||||
|
|
||||||
You should now be able to access Træfik on port 80 of your Minikube instance when using the DaemonSet:
|
You should now be able to access Træfik on port 80 of your Minikube instance when using the DaemonSet:
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
curl $(minikube ip)
|
curl $(minikube ip)
|
||||||
```
|
```
|
||||||
```
|
|
||||||
|
```shell
|
||||||
404 page not found
|
404 page not found
|
||||||
```
|
```
|
||||||
|
|
||||||
If you decided to use the deployment, then you need to target the correct NodePort, which can be seen when you execute `kubectl get services --namespace=kube-system`.
|
If you decided to use the deployment, then you need to target the correct NodePort, which can be seen when you execute `kubectl get services --namespace=kube-system`.
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
curl $(minikube ip):<NODEPORT>
|
curl $(minikube ip):<NODEPORT>
|
||||||
```
|
```
|
||||||
```
|
|
||||||
|
```shell
|
||||||
404 page not found
|
404 page not found
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -273,19 +278,20 @@ All further examples below assume a DaemonSet installation. Deployment users wil
|
||||||
|
|
||||||
## Deploy Træfik using Helm Chart
|
## Deploy Træfik using Helm Chart
|
||||||
|
|
||||||
Instead of installing Træfik via an own object, you can also use the Træfik Helm chart.
|
!!! note
|
||||||
|
The Helm Chart is maintained by the community, not the Traefik project maintainers.
|
||||||
|
|
||||||
This allows more complex configuration via Kubernetes [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configmap/) and enabled TLS certificates.
|
Instead of installing Træfik via Kubernetes object directly, you can also use the Træfik Helm chart.
|
||||||
|
|
||||||
Install Træfik chart by:
|
Install the Træfik chart by:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
helm install stable/traefik
|
helm install stable/traefik
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information, check out [the doc](https://github.com/kubernetes/charts/tree/master/stable/traefik).
|
For more information, check out [the documentation](https://github.com/kubernetes/charts/tree/master/stable/traefik).
|
||||||
|
|
||||||
## Submitting An Ingress to the cluster.
|
## Submitting an Ingress to the Cluster
|
||||||
|
|
||||||
Lets start by creating a Service and an Ingress that will expose the [Træfik Web UI](https://github.com/containous/traefik#web-ui).
|
Lets start by creating a Service and an Ingress that will expose the [Træfik Web UI](https://github.com/containous/traefik#web-ui).
|
||||||
|
|
||||||
|
@ -318,22 +324,23 @@ spec:
|
||||||
serviceName: traefik-web-ui
|
serviceName: traefik-web-ui
|
||||||
servicePort: 80
|
servicePort: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
[examples/k8s/ui.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/ui.yaml)
|
[examples/k8s/ui.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/ui.yaml)
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/ui.yaml
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/ui.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Now lets setup an entry in our /etc/hosts file to route `traefik-ui.minikube` to our cluster.
|
Now lets setup an entry in our `/etc/hosts` file to route `traefik-ui.minikube` to our cluster.
|
||||||
|
|
||||||
In production you would want to set up real dns entries.
|
In production you would want to set up real DNS entries.
|
||||||
You can get the ip address of your minikube instance by running `minikube ip`
|
You can get the IP address of your minikube instance by running `minikube ip`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
echo "$(minikube ip) traefik-ui.minikube" | sudo tee -a /etc/hosts
|
echo "$(minikube ip) traefik-ui.minikube" | sudo tee -a /etc/hosts
|
||||||
```
|
```
|
||||||
|
|
||||||
We should now be able to visit [traefik-ui.minikube](http://traefik-ui.minikube) in the browser and view the Træfik Web UI.
|
We should now be able to visit [traefik-ui.minikube](http://traefik-ui.minikube) in the browser and view the Træfik web UI.
|
||||||
|
|
||||||
### Add a TLS Certificate to the Ingress
|
### Add a TLS Certificate to the Ingress
|
||||||
|
|
||||||
|
@ -382,11 +389,9 @@ If there are any errors while loading the TLS section of an ingress, the whole i
|
||||||
|
|
||||||
## Basic Authentication
|
## Basic Authentication
|
||||||
|
|
||||||
It's possible to add additional authentication annotations in the Ingress rule.
|
It's possible to protect access to Traefik through basic authentication. (See the [Kubernetes Ingress](/configuration/backends/kubernetes) configuration page for syntactical details and restrictions.)
|
||||||
The source of the authentication is a secret that contains usernames and passwords inside the key auth.
|
|
||||||
To read about basic auth limitations see the [Kubernetes Ingress](/configuration/backends/kubernetes) configuration page.
|
|
||||||
|
|
||||||
#### Creating the Secret
|
### Creating the Secret
|
||||||
|
|
||||||
A. Use `htpasswd` to create a file containing the username and the base64-encoded password:
|
A. Use `htpasswd` to create a file containing the username and the base64-encoded password:
|
||||||
|
|
||||||
|
@ -400,25 +405,28 @@ You will be prompted for a password which you will have to enter twice.
|
||||||
```shell
|
```shell
|
||||||
cat auth
|
cat auth
|
||||||
```
|
```
|
||||||
```
|
|
||||||
|
```shell
|
||||||
myusername:$apr1$78Jyn/1K$ERHKVRPPlzAX8eBtLuvRZ0
|
myusername:$apr1$78Jyn/1K$ERHKVRPPlzAX8eBtLuvRZ0
|
||||||
```
|
```
|
||||||
|
|
||||||
B. Now use `kubectl` to create a secret in the monitoring namespace using the file created by `htpasswd`.
|
B. Now use `kubectl` to create a secret in the `monitoring` namespace using the file created by `htpasswd`.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
kubectl create secret generic mysecret --from-file auth --namespace=monitoring
|
kubectl create secret generic mysecret --from-file auth --namespace=monitoring
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Secret must be in same namespace as the ingress rule.
|
Secret must be in same namespace as the Ingress object.
|
||||||
|
|
||||||
C. Create the ingress using the following annotations to specify basic auth and that the username and password is stored in `mysecret`.
|
C. Attach the following annotations to the Ingress object:
|
||||||
|
|
||||||
- `ingress.kubernetes.io/auth-type: "basic"`
|
- `ingress.kubernetes.io/auth-type: "basic"`
|
||||||
- `ingress.kubernetes.io/auth-secret: "mysecret"`
|
- `ingress.kubernetes.io/auth-secret: "mysecret"`
|
||||||
|
|
||||||
Following is a full ingress example based on Prometheus:
|
They specify basic authentication and reference the Secret `mysecret` containing the credentials.
|
||||||
|
|
||||||
|
Following is a full Ingress example based on Prometheus:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
|
@ -440,17 +448,17 @@ spec:
|
||||||
servicePort: 9090
|
servicePort: 9090
|
||||||
```
|
```
|
||||||
|
|
||||||
You can apply the example ingress as following:
|
You can apply the example as following:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
kubectl create -f prometheus-ingress.yaml -n monitoring
|
kubectl create -f prometheus-ingress.yaml -n monitoring
|
||||||
```
|
```
|
||||||
|
|
||||||
## Name based routing
|
## Name-based Routing
|
||||||
|
|
||||||
In this example we are going to setup websites for 3 of the United Kingdoms best loved cheeses, Cheddar, Stilton and Wensleydale.
|
In this example we are going to setup websites for three of the United Kingdoms best loved cheeses: Cheddar, Stilton, and Wensleydale.
|
||||||
|
|
||||||
First lets start by launching the 3 pods for the cheese websites.
|
First lets start by launching the pods for the cheese websites.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
|
@ -532,13 +540,14 @@ spec:
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
[examples/k8s/cheese-deployments.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-deployments.yaml)
|
[examples/k8s/cheese-deployments.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-deployments.yaml)
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-deployments.yaml
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-deployments.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Next we need to setup a service for each of the cheese pods.
|
Next we need to setup a Service for each of the cheese pods.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
|
@ -587,7 +596,6 @@ spec:
|
||||||
!!! note
|
!!! note
|
||||||
We also set a [circuit breaker expression](/basics/#backends) for one of the backends by setting the `traefik.backend.circuitbreaker` annotation on the service.
|
We also set a [circuit breaker expression](/basics/#backends) for one of the backends by setting the `traefik.backend.circuitbreaker` annotation on the service.
|
||||||
|
|
||||||
|
|
||||||
[examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml)
|
[examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml)
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -627,6 +635,7 @@ spec:
|
||||||
serviceName: wensleydale
|
serviceName: wensleydale
|
||||||
servicePort: http
|
servicePort: http
|
||||||
```
|
```
|
||||||
|
|
||||||
[examples/k8s/cheese-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-ingress.yaml)
|
[examples/k8s/cheese-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-ingress.yaml)
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
@ -637,7 +646,7 @@ kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/exa
|
||||||
```
|
```
|
||||||
|
|
||||||
Now visit the [Træfik dashboard](http://traefik-ui.minikube/) and you should see a frontend for each host.
|
Now visit the [Træfik dashboard](http://traefik-ui.minikube/) and you should see a frontend for each host.
|
||||||
Along with a backend listing for each service with a Server set up for each pod.
|
Along with a backend listing for each service with a server set up for each pod.
|
||||||
|
|
||||||
If you edit your `/etc/hosts` again you should be able to access the cheese websites in your browser.
|
If you edit your `/etc/hosts` again you should be able to access the cheese websites in your browser.
|
||||||
|
|
||||||
|
@ -645,11 +654,11 @@ If you edit your `/etc/hosts` again you should be able to access the cheese webs
|
||||||
echo "$(minikube ip) stilton.minikube cheddar.minikube wensleydale.minikube" | sudo tee -a /etc/hosts
|
echo "$(minikube ip) stilton.minikube cheddar.minikube wensleydale.minikube" | sudo tee -a /etc/hosts
|
||||||
```
|
```
|
||||||
|
|
||||||
* [Stilton](http://stilton.minikube/)
|
- [Stilton](http://stilton.minikube/)
|
||||||
* [Cheddar](http://cheddar.minikube/)
|
- [Cheddar](http://cheddar.minikube/)
|
||||||
* [Wensleydale](http://wensleydale.minikube/)
|
- [Wensleydale](http://wensleydale.minikube/)
|
||||||
|
|
||||||
## Path based routing
|
## Path-based Routing
|
||||||
|
|
||||||
Now lets suppose that our fictional client has decided that while they are super happy about our cheesy web design, when they asked for 3 websites they had not really bargained on having to buy 3 domain names.
|
Now lets suppose that our fictional client has decided that while they are super happy about our cheesy web design, when they asked for 3 websites they had not really bargained on having to buy 3 domain names.
|
||||||
|
|
||||||
|
@ -681,10 +690,11 @@ spec:
|
||||||
serviceName: wensleydale
|
serviceName: wensleydale
|
||||||
servicePort: http
|
servicePort: http
|
||||||
```
|
```
|
||||||
|
|
||||||
[examples/k8s/cheeses-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheeses-ingress.yaml)
|
[examples/k8s/cheeses-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheeses-ingress.yaml)
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
we are configuring Træfik to strip the prefix from the url path with the `traefik.frontend.rule.type` annotation so that we can use the containers from the previous example without modification.
|
We are configuring Træfik to strip the prefix from the url path with the `traefik.frontend.rule.type` annotation so that we can use the containers from the previous example without modification.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheeses-ingress.yaml
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheeses-ingress.yaml
|
||||||
|
@ -696,14 +706,14 @@ echo "$(minikube ip) cheeses.minikube" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
You should now be able to visit the websites in your browser.
|
You should now be able to visit the websites in your browser.
|
||||||
|
|
||||||
* [cheeses.minikube/stilton](http://cheeses.minikube/stilton/)
|
- [cheeses.minikube/stilton](http://cheeses.minikube/stilton/)
|
||||||
* [cheeses.minikube/cheddar](http://cheeses.minikube/cheddar/)
|
- [cheeses.minikube/cheddar](http://cheeses.minikube/cheddar/)
|
||||||
* [cheeses.minikube/wensleydale](http://cheeses.minikube/wensleydale/)
|
- [cheeses.minikube/wensleydale](http://cheeses.minikube/wensleydale/)
|
||||||
|
|
||||||
## Specifying priority for routing
|
## Specifying Routing Priorities
|
||||||
|
|
||||||
Sometimes you need to specify priority for ingress route, especially when handling wildcard routes.
|
Sometimes you need to specify priority for ingress routes, especially when handling wildcard routes.
|
||||||
This can be done by adding annotation `traefik.frontend.priority`, i.e.:
|
This can be done by adding the `traefik.frontend.priority` annotation, i.e.:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
|
@ -738,7 +748,7 @@ spec:
|
||||||
servicePort: http
|
servicePort: http
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that priority values must be quoted to avoid them being interpreted as numbers (which are illegal for annotations).
|
Note that priority values must be quoted to avoid numeric interpretation (which are illegal for annotations).
|
||||||
|
|
||||||
## Forwarding to ExternalNames
|
## Forwarding to ExternalNames
|
||||||
|
|
||||||
|
@ -746,26 +756,25 @@ When specifying an [ExternalName](https://kubernetes.io/docs/concepts/services-n
|
||||||
Træfik will forward requests to the given host accordingly and use HTTPS when the Service port matches 443.
|
Træfik will forward requests to the given host accordingly and use HTTPS when the Service port matches 443.
|
||||||
This still requires setting up a proper port mapping on the Service from the Ingress port to the (external) Service port.
|
This still requires setting up a proper port mapping on the Service from the Ingress port to the (external) Service port.
|
||||||
|
|
||||||
## Disable passing the Host header
|
## Disable passing the Host Header
|
||||||
|
|
||||||
By default Træfik will pass the incoming Host header on to the upstream resource.
|
By default Træfik will pass the incoming Host header to the upstream resource.
|
||||||
|
|
||||||
There are times however where you may not want this to be the case.
|
However, there are times when you may not want this to be the case. For example, if your service is of the ExternalName type.
|
||||||
For example if your service is of the ExternalName type.
|
|
||||||
|
|
||||||
### Disable entirely
|
### Disable globally
|
||||||
|
|
||||||
Add the following to your toml config:
|
Add the following to your TOML configuration file:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
disablePassHostHeaders = true
|
disablePassHostHeaders = true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Disable per ingress
|
### Disable per Ingress
|
||||||
|
|
||||||
To disable passing the Host header per ingress resource set the `traefik.frontend.passHostHeader` annotation on your ingress to `false`.
|
To disable passing the Host header per ingress resource set the `traefik.frontend.passHostHeader` annotation on your ingress to `"false"`.
|
||||||
|
|
||||||
Here is an example ingress definition:
|
Here is an example definition:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
|
@ -801,12 +810,11 @@ spec:
|
||||||
externalName: static.otherdomain.com
|
externalName: static.otherdomain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
If you were to visit `example.com/static` the request would then be passed onto `static.otherdomain.com/static` and s`tatic.otherdomain.com` would receive the request with the Host header being `static.otherdomain.com`.
|
If you were to visit `example.com/static` the request would then be passed on to `static.otherdomain.com/static`, and `static.otherdomain.com` would receive the request with the Host header being `static.otherdomain.com`.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
The per ingress annotation overides whatever the global value is set to.
|
The per-ingress annotation overrides whatever the global value is set to.
|
||||||
So you could set `disablePassHostHeaders` to `true` in your toml file and then enable passing
|
So you could set `disablePassHostHeaders` to `true` in your TOML configuration file and then enable passing the host header per ingress if you wanted.
|
||||||
the host header per ingress if you wanted.
|
|
||||||
|
|
||||||
## Partitioning the Ingress object space
|
## Partitioning the Ingress object space
|
||||||
|
|
||||||
|
|
|
@ -70,10 +70,13 @@ logLevel = "DEBUG"
|
||||||
defaultEntryPoints = ["http", "https"]
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
[entryPoints.api]
|
||||||
|
address = ":8081"
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
|
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
[[entryPoints.https.tls.certificates]]
|
[[entryPoints.https.tls.certificates]]
|
||||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
@ -94,8 +97,8 @@ defaultEntryPoints = ["http", "https"]
|
||||||
watch = true
|
watch = true
|
||||||
prefix = "traefik"
|
prefix = "traefik"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8081"
|
entrypoint = "api"
|
||||||
```
|
```
|
||||||
|
|
||||||
And there, the same global configuration in the Key-value Store (using `prefix = "traefik"`):
|
And there, the same global configuration in the Key-value Store (using `prefix = "traefik"`):
|
||||||
|
@ -105,6 +108,7 @@ And there, the same global configuration in the Key-value Store (using `prefix =
|
||||||
| `/traefik/loglevel` | `DEBUG` |
|
| `/traefik/loglevel` | `DEBUG` |
|
||||||
| `/traefik/defaultentrypoints/0` | `http` |
|
| `/traefik/defaultentrypoints/0` | `http` |
|
||||||
| `/traefik/defaultentrypoints/1` | `https` |
|
| `/traefik/defaultentrypoints/1` | `https` |
|
||||||
|
| `/traefik/entrypoints/api/address` | `:8081` |
|
||||||
| `/traefik/entrypoints/http/address` | `:80` |
|
| `/traefik/entrypoints/http/address` | `:80` |
|
||||||
| `/traefik/entrypoints/https/address` | `:443` |
|
| `/traefik/entrypoints/https/address` | `:443` |
|
||||||
| `/traefik/entrypoints/https/tls/certificates/0/certfile` | `integration/fixtures/https/snitest.com.cert` |
|
| `/traefik/entrypoints/https/tls/certificates/0/certfile` | `integration/fixtures/https/snitest.com.cert` |
|
||||||
|
@ -115,7 +119,7 @@ And there, the same global configuration in the Key-value Store (using `prefix =
|
||||||
| `/traefik/consul/endpoint` | `127.0.0.1:8500` |
|
| `/traefik/consul/endpoint` | `127.0.0.1:8500` |
|
||||||
| `/traefik/consul/watch` | `true` |
|
| `/traefik/consul/watch` | `true` |
|
||||||
| `/traefik/consul/prefix` | `traefik` |
|
| `/traefik/consul/prefix` | `traefik` |
|
||||||
| `/traefik/web/address` | `:8081` |
|
| `/traefik/api/entrypoint` | `api` |
|
||||||
|
|
||||||
In case you are setting key values manually:
|
In case you are setting key values manually:
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ docker-machine ssh manager "docker service create \
|
||||||
--docker.swarmmode \
|
--docker.swarmmode \
|
||||||
--docker.domain=traefik \
|
--docker.domain=traefik \
|
||||||
--docker.watch \
|
--docker.watch \
|
||||||
--web"
|
--api"
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's explain this command:
|
Let's explain this command:
|
||||||
|
@ -102,7 +102,7 @@ Let's explain this command:
|
||||||
| `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock` | we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. |
|
| `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock` | we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. |
|
||||||
| `--network traefik-net` | we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. |
|
| `--network traefik-net` | we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. |
|
||||||
| `--docker` | enable docker backend, and `--docker.swarmmode` to enable the swarm mode on Træfik. |
|
| `--docker` | enable docker backend, and `--docker.swarmmode` to enable the swarm mode on Træfik. |
|
||||||
| `--web` | activate the webUI on port 8080 |
|
| `--api | activate the webUI on port 8080 |
|
||||||
|
|
||||||
|
|
||||||
## Deploy your apps
|
## Deploy your apps
|
||||||
|
|
|
@ -93,7 +93,7 @@ docker $(docker-machine config mhs-demo0) run \
|
||||||
--docker.tls.key=/ssl/server-key.pem \
|
--docker.tls.key=/ssl/server-key.pem \
|
||||||
--docker.tls.insecureSkipVerify \
|
--docker.tls.insecureSkipVerify \
|
||||||
--docker.watch \
|
--docker.watch \
|
||||||
--web
|
--api
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's explain this command:
|
Let's explain this command:
|
||||||
|
@ -107,7 +107,7 @@ Let's explain this command:
|
||||||
| `--docker` | enable docker backend |
|
| `--docker` | enable docker backend |
|
||||||
| `--docker.endpoint=tcp://172.18.0.1:2376` | connect to the swarm master using the docker_gwbridge network |
|
| `--docker.endpoint=tcp://172.18.0.1:2376` | connect to the swarm master using the docker_gwbridge network |
|
||||||
| `--docker.tls` | enable TLS using the docker-machine keys |
|
| `--docker.tls` | enable TLS using the docker-machine keys |
|
||||||
| `--web` | activate the webUI on port 8080 |
|
| `--api` | activate the webUI on port 8080 |
|
||||||
|
|
||||||
|
|
||||||
## Deploy your apps
|
## Deploy your apps
|
||||||
|
|
|
@ -19,6 +19,8 @@ entryPoint = "https"
|
||||||
onDemand = false
|
onDemand = false
|
||||||
OnHostRule = true
|
OnHostRule = true
|
||||||
caServer = "http://traefik.localhost.com:4000/directory"
|
caServer = "http://traefik.localhost.com:4000/directory"
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint="http"
|
||||||
|
|
||||||
|
|
||||||
[web]
|
[web]
|
||||||
|
|
|
@ -78,6 +78,7 @@ services :
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
- "5001:443" # Needed for SNI challenge
|
- "5001:443" # Needed for SNI challenge
|
||||||
|
- "5002:80" # Needed for HTTP challenge
|
||||||
expose:
|
expose:
|
||||||
- "8080"
|
- "8080"
|
||||||
labels:
|
labels:
|
||||||
|
|
|
@ -136,11 +136,13 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "443"
|
- "443"
|
||||||
- "5001"
|
- "5001"
|
||||||
|
- "5002"
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
- "5001:443" # Needed for SNI challenge
|
- "5001:443" # Needed for SNI challenge
|
||||||
|
- "5002:80" # Needed for HTTP challenge
|
||||||
networks:
|
networks:
|
||||||
net:
|
net:
|
||||||
ipv4_address: 10.0.1.8
|
ipv4_address: 10.0.1.8
|
||||||
|
@ -157,6 +159,7 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "443"
|
- "443"
|
||||||
- "5001"
|
- "5001"
|
||||||
|
- "5002"
|
||||||
ports:
|
ports:
|
||||||
- "88:80"
|
- "88:80"
|
||||||
- "8888:8080"
|
- "8888:8080"
|
||||||
|
|
|
@ -15,6 +15,8 @@ storage = "traefik/acme/account"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
OnHostRule = true
|
OnHostRule = true
|
||||||
caServer = "http://traefik.boulder.com:4000/directory"
|
caServer = "http://traefik.boulder.com:4000/directory"
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint="http"
|
||||||
|
|
||||||
|
|
||||||
[web]
|
[web]
|
||||||
|
|
|
@ -49,6 +49,7 @@ type BackendHealthCheck struct {
|
||||||
|
|
||||||
//HealthCheck struct
|
//HealthCheck struct
|
||||||
type HealthCheck struct {
|
type HealthCheck struct {
|
||||||
|
mutex sync.Mutex
|
||||||
Backends map[string]*BackendHealthCheck
|
Backends map[string]*BackendHealthCheck
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
@ -77,14 +78,16 @@ func NewBackendHealthCheck(options Options, backendName string) *BackendHealthCh
|
||||||
|
|
||||||
//SetBackendsConfiguration set backends configuration
|
//SetBackendsConfiguration set backends configuration
|
||||||
func (hc *HealthCheck) SetBackendsConfiguration(parentCtx context.Context, backends map[string]*BackendHealthCheck) {
|
func (hc *HealthCheck) SetBackendsConfiguration(parentCtx context.Context, backends map[string]*BackendHealthCheck) {
|
||||||
|
hc.mutex.Lock()
|
||||||
hc.Backends = backends
|
hc.Backends = backends
|
||||||
if hc.cancel != nil {
|
if hc.cancel != nil {
|
||||||
hc.cancel()
|
hc.cancel()
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(parentCtx)
|
ctx, cancel := context.WithCancel(parentCtx)
|
||||||
hc.cancel = cancel
|
hc.cancel = cancel
|
||||||
|
hc.mutex.Unlock()
|
||||||
|
|
||||||
for _, backend := range hc.Backends {
|
for _, backend := range backends {
|
||||||
currentBackend := backend
|
currentBackend := backend
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
hc.execute(ctx, currentBackend)
|
hc.execute(ctx, currentBackend)
|
||||||
|
|
|
@ -72,6 +72,26 @@ func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificate(c *check.C) {
|
||||||
s.retrieveAcmeCertificate(c, testCase)
|
s.retrieveAcmeCertificate(c, testCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test OnDemand option with none provided certificate and challenge HTTP-01
|
||||||
|
func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateHTTP01(c *check.C) {
|
||||||
|
testCase := AcmeTestCase{
|
||||||
|
traefikConfFilePath: "fixtures/acme/acme_http01.toml",
|
||||||
|
onDemand: true,
|
||||||
|
domainToCheck: acmeDomain}
|
||||||
|
|
||||||
|
s.retrieveAcmeCertificate(c, testCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test OnHostRule option with none provided certificate and challenge HTTP-01
|
||||||
|
func (s *AcmeSuite) TestOnHostRuleRetrieveAcmeCertificateHTTP01(c *check.C) {
|
||||||
|
testCase := AcmeTestCase{
|
||||||
|
traefikConfFilePath: "fixtures/acme/acme_http01.toml",
|
||||||
|
onDemand: false,
|
||||||
|
domainToCheck: acmeDomain}
|
||||||
|
|
||||||
|
s.retrieveAcmeCertificate(c, testCase)
|
||||||
|
}
|
||||||
|
|
||||||
// Test OnDemand option with a wildcard provided certificate
|
// Test OnDemand option with a wildcard provided certificate
|
||||||
func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateWithWildcard(c *check.C) {
|
func (s *AcmeSuite) TestOnDemandRetrieveAcmeCertificateWithWildcard(c *check.C) {
|
||||||
testCase := AcmeTestCase{
|
testCase := AcmeTestCase{
|
||||||
|
|
|
@ -294,7 +294,10 @@ func (s *ConsulSuite) skipTestGlobalConfigurationWithClientTLS(c *check.C) {
|
||||||
s.setupConsulTLS(c)
|
s.setupConsulTLS(c)
|
||||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||||
|
|
||||||
err := s.kv.Put("traefik/web/address", []byte(":8081"), nil)
|
err := s.kv.Put("traefik/api/entrypoint", []byte("api"), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = s.kv.Put("traefik/entrypoints/api/address", []byte(":8081"), nil)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
// wait for consul
|
// wait for consul
|
||||||
|
@ -341,7 +344,7 @@ func (s *ConsulSuite) TestCommandStoreConfig(c *check.C) {
|
||||||
"/traefik/loglevel": "DEBUG",
|
"/traefik/loglevel": "DEBUG",
|
||||||
"/traefik/defaultentrypoints/0": "http",
|
"/traefik/defaultentrypoints/0": "http",
|
||||||
"/traefik/entrypoints/http/address": ":8000",
|
"/traefik/entrypoints/http/address": ":8000",
|
||||||
"/traefik/web/address": ":8080",
|
"/traefik/api/entrypoint": "traefik",
|
||||||
"/traefik/consul/endpoint": consulHost + ":8500",
|
"/traefik/consul/endpoint": consulHost + ":8500",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -411,7 +411,7 @@ func (s *Etcd3Suite) TestCommandStoreConfig(c *check.C) {
|
||||||
"/traefik/loglevel": "DEBUG",
|
"/traefik/loglevel": "DEBUG",
|
||||||
"/traefik/defaultentrypoints/0": "http",
|
"/traefik/defaultentrypoints/0": "http",
|
||||||
"/traefik/entrypoints/http/address": ":8000",
|
"/traefik/entrypoints/http/address": ":8000",
|
||||||
"/traefik/web/address": ":8080",
|
"/traefik/api/entrypoint": "traefik",
|
||||||
"/traefik/etcd/endpoint": ipEtcd + ":4001",
|
"/traefik/etcd/endpoint": ipEtcd + ":4001",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -419,7 +419,7 @@ func (s *EtcdSuite) TestCommandStoreConfig(c *check.C) {
|
||||||
"/traefik/loglevel": "DEBUG",
|
"/traefik/loglevel": "DEBUG",
|
||||||
"/traefik/defaultentrypoints/0": "http",
|
"/traefik/defaultentrypoints/0": "http",
|
||||||
"/traefik/entrypoints/http/address": ":8000",
|
"/traefik/entrypoints/http/address": ":8000",
|
||||||
"/traefik/web/address": ":8080",
|
"/traefik/api/entrypoint": "traefik",
|
||||||
"/traefik/etcd/endpoint": etcdHost + ":4001",
|
"/traefik/etcd/endpoint": etcdHost + ":4001",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
integration/fixtures/acme/acme_http01.toml
Normal file
35
integration/fixtures/acme/acme_http01.toml
Normal 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"
|
|
@ -5,6 +5,8 @@ logLevel = "DEBUG"
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
[entryPoints.api]
|
||||||
|
address = ":8081"
|
||||||
|
|
||||||
|
|
||||||
[consul]
|
[consul]
|
||||||
|
@ -12,5 +14,5 @@ logLevel = "DEBUG"
|
||||||
watch = true
|
watch = true
|
||||||
prefix = "traefik"
|
prefix = "traefik"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8081"
|
entryPoint = "api"
|
||||||
|
|
|
@ -3,6 +3,8 @@ defaultEntryPoints = ["http","https"]
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
[entryPoints.api]
|
||||||
|
address = ":8081"
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
|
@ -16,5 +18,5 @@ logLevel = "DEBUG"
|
||||||
prefix = "traefik"
|
prefix = "traefik"
|
||||||
watch = true
|
watch = true
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8081"
|
entryPoint = "api"
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
defaultEntryPoints = ["http"]
|
defaultEntryPoints = ["http"]
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
|
|
|
@ -6,7 +6,7 @@ logLevel = "DEBUG"
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
|
|
||||||
[docker]
|
[docker]
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ logLevel = "DEBUG"
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8080"
|
address = ":8080"
|
||||||
|
[entryPoints.api]
|
||||||
|
address = ":8081"
|
||||||
|
|
||||||
[dynamodb]
|
[dynamodb]
|
||||||
AccessKeyID = "key"
|
AccessKeyID = "key"
|
||||||
|
@ -12,5 +14,5 @@ logLevel = "DEBUG"
|
||||||
Endpoint = "{{.DynamoURL}}"
|
Endpoint = "{{.DynamoURL}}"
|
||||||
Region = "us-east-1"
|
Region = "us-east-1"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8081"
|
entryPoint = "api"
|
||||||
|
|
|
@ -5,6 +5,8 @@ logLevel = "DEBUG"
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
[entryPoints.api]
|
||||||
|
address = ":8081"
|
||||||
|
|
||||||
|
|
||||||
[etcd]
|
[etcd]
|
||||||
|
@ -13,5 +15,5 @@ logLevel = "DEBUG"
|
||||||
watch = true
|
watch = true
|
||||||
useAPIV3 = {{.UseAPIV3}}
|
useAPIV3 = {{.UseAPIV3}}
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8081"
|
entryPoint = "api"
|
|
@ -3,6 +3,8 @@ defaultEntryPoints = ["http","https"]
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
[entryPoints.api]
|
||||||
|
address = ":8081"
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
|
@ -16,5 +18,6 @@ logLevel = "DEBUG"
|
||||||
# prefix = "/traefik"
|
# prefix = "/traefik"
|
||||||
# watch = true
|
# watch = true
|
||||||
|
|
||||||
[web]
|
|
||||||
address = ":8081"
|
[api]
|
||||||
|
entryPoint = "api"
|
|
@ -10,5 +10,4 @@ logLevel = "DEBUG"
|
||||||
[eureka]
|
[eureka]
|
||||||
endpoint = "http://{{.EurekaHost}}:8761/eureka"
|
endpoint = "http://{{.EurekaHost}}:8761/eureka"
|
||||||
delay = "1s"
|
delay = "1s"
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
|
@ -11,8 +11,7 @@ RootCAs = [ """{{ .CertContent }}""" ]
|
||||||
keyFile = """{{ .KeyContent }}"""
|
keyFile = """{{ .KeyContent }}"""
|
||||||
|
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,7 @@ InsecureSkipVerify = true
|
||||||
keyFile = """{{ .KeyContent }}"""
|
keyFile = """{{ .KeyContent }}"""
|
||||||
|
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,7 @@ logLevel = "DEBUG"
|
||||||
[entryPoints.http2]
|
[entryPoints.http2]
|
||||||
address = ":9000"
|
address = ":9000"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
[backends]
|
[backends]
|
||||||
|
|
|
@ -8,8 +8,7 @@ logLevel = "DEBUG"
|
||||||
[entryPoints.http2]
|
[entryPoints.http2]
|
||||||
address = ":9000"
|
address = ":9000"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
[backends]
|
[backends]
|
||||||
|
|
|
@ -6,8 +6,7 @@ logLevel = "DEBUG"
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
[backends]
|
[backends]
|
||||||
|
|
|
@ -6,8 +6,7 @@ logLevel = "DEBUG"
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
[backends]
|
[backends]
|
||||||
|
|
|
@ -16,8 +16,7 @@ defaultEntryPoints = ["https"]
|
||||||
certFile = "fixtures/https/snitest.org.cert"
|
certFile = "fixtures/https/snitest.org.cert"
|
||||||
keyFile = "fixtures/https/snitest.org.key"
|
keyFile = "fixtures/https/snitest.org.key"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,7 @@ defaultEntryPoints = ["https"]
|
||||||
certFile = "fixtures/https/snitest.org.cert"
|
certFile = "fixtures/https/snitest.org.cert"
|
||||||
keyFile = "fixtures/https/snitest.org.key"
|
keyFile = "fixtures/https/snitest.org.key"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,7 @@ defaultEntryPoints = ["https"]
|
||||||
certFile = "fixtures/https/snitest.org.cert"
|
certFile = "fixtures/https/snitest.org.cert"
|
||||||
keyFile = "fixtures/https/snitest.org.key"
|
keyFile = "fixtures/https/snitest.org.key"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,7 @@ defaultEntryPoints = ["https"]
|
||||||
address = ":8443"
|
address = ":8443"
|
||||||
[entryPoints.https02.tls]
|
[entryPoints.https02.tls]
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,7 @@ defaultEntryPoints = ["https"]
|
||||||
certFile = "fixtures/https/snitest.org.cert"
|
certFile = "fixtures/https/snitest.org.cert"
|
||||||
keyFile = "fixtures/https/snitest.org.key"
|
keyFile = "fixtures/https/snitest.org.key"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,7 @@ fblo6RBxUQ==
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8081"
|
address = ":8081"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,7 @@ RootCAs = [ "fixtures/https/rootcas/local.crt"]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8081"
|
address = ":8081"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,16 @@ defaultEntryPoints = ["http"]
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
[entryPoints.api]
|
||||||
|
address = ":7888"
|
||||||
|
|
||||||
checkNewVersion = false
|
checkNewVersion = false
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# Web configuration backend
|
# Api configuration backend
|
||||||
################################################################
|
################################################################
|
||||||
[web]
|
[api]
|
||||||
address = ":7888"
|
entryPoint = "api"
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# File configuration backend
|
# File configuration backend
|
||||||
|
|
|
@ -5,9 +5,11 @@ logLevel = "DEBUG"
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
[entryPoints.api]
|
||||||
|
address = ":9090"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":9090"
|
entryPoint = "api"
|
||||||
|
|
||||||
[marathon]
|
[marathon]
|
||||||
endpoint = "{{.MarathonURL}}"
|
endpoint = "{{.MarathonURL}}"
|
||||||
|
|
|
@ -7,8 +7,7 @@ address = ":8000"
|
||||||
[entryPoints.http.proxyProtocol]
|
[entryPoints.http.proxyProtocol]
|
||||||
trustedIPs = ["{{.HaproxyIP}}"]
|
trustedIPs = ["{{.HaproxyIP}}"]
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ address = ":8000"
|
||||||
[entryPoints.http.proxyProtocol]
|
[entryPoints.http.proxyProtocol]
|
||||||
trustedIPs = ["1.2.3.4"]
|
trustedIPs = ["1.2.3.4"]
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,4 @@ defaultEntryPoints = ["http"]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
|
@ -8,8 +8,7 @@ defaultEntryPoints = ["http"]
|
||||||
[accessLog]
|
[accessLog]
|
||||||
format = "json"
|
format = "json"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[forwardingTimeouts]
|
[forwardingTimeouts]
|
||||||
dialTimeout = "300ms"
|
dialTimeout = "300ms"
|
||||||
|
|
|
@ -7,8 +7,7 @@ logLevel = "DEBUG"
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,7 @@ InsecureSkipVerify=true
|
||||||
certFile = "resources/tls/local.cert"
|
certFile = "resources/tls/local.cert"
|
||||||
keyFile = "resources/tls/local.key"
|
keyFile = "resources/tls/local.key"
|
||||||
|
|
||||||
[web]
|
[api]
|
||||||
address = ":8080"
|
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
|
5
integration/resources/compose/addprefix.yml
Normal file
5
integration/resources/compose/addprefix.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
whoami1:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.frontend.rule=AddPrefix:/whoami;PathPrefix:/
|
|
@ -2,6 +2,7 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -218,18 +219,24 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
}
|
}
|
||||||
|
|
||||||
eventsc, errc := dockerClient.Events(ctx, options)
|
eventsc, errc := dockerClient.Events(ctx, options)
|
||||||
for event := range eventsc {
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-eventsc:
|
||||||
if event.Action == "start" ||
|
if event.Action == "start" ||
|
||||||
event.Action == "die" ||
|
event.Action == "die" ||
|
||||||
strings.HasPrefix(event.Action, "health_status") {
|
strings.HasPrefix(event.Action, "health_status") {
|
||||||
startStopHandle(event)
|
startStopHandle(event)
|
||||||
}
|
}
|
||||||
|
case err := <-errc:
|
||||||
|
if err == io.EOF {
|
||||||
|
log.Debug("Provider event stream closed")
|
||||||
}
|
}
|
||||||
if err := <-errc; err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
notify := func(err error, time time.Duration) {
|
notify := func(err error, time time.Duration) {
|
||||||
|
|
|
@ -82,11 +82,11 @@ func (p *Provider) newK8sClient() (Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" {
|
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" {
|
||||||
log.Infof("Creating in-cluster Provider client%s\n", withEndpoint)
|
log.Infof("Creating in-cluster Provider client%s", withEndpoint)
|
||||||
return NewInClusterClient(p.Endpoint)
|
return NewInClusterClient(p.Endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Creating cluster-external Provider client%s\n", withEndpoint)
|
log.Infof("Creating cluster-external Provider client%s", withEndpoint)
|
||||||
return NewExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath)
|
return NewExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -770,6 +770,10 @@ func (s *Server) addInternalPublicRoutes(entryPointName string, router *mux.Rout
|
||||||
if s.globalConfiguration.Ping != nil && s.globalConfiguration.Ping.EntryPoint != "" && s.globalConfiguration.Ping.EntryPoint == entryPointName {
|
if s.globalConfiguration.Ping != nil && s.globalConfiguration.Ping.EntryPoint != "" && s.globalConfiguration.Ping.EntryPoint == entryPointName {
|
||||||
s.globalConfiguration.Ping.AddRoutes(router)
|
s.globalConfiguration.Ping.AddRoutes(router)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.globalConfiguration.ACME != nil && s.globalConfiguration.ACME.HTTPChallenge != nil && s.globalConfiguration.ACME.HTTPChallenge.EntryPoint == entryPointName {
|
||||||
|
s.globalConfiguration.ACME.AddRoutes(router)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher, middlewares []negroni.Handler, internalMiddlewares []negroni.Handler) (*http.Server, net.Listener, error) {
|
func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher, middlewares []negroni.Handler, internalMiddlewares []negroni.Handler) (*http.Server, net.Listener, error) {
|
||||||
|
|
13
vendor/github.com/abbot/go-http-auth/digest.go
generated
vendored
13
vendor/github.com/abbot/go-http-auth/digest.go
generated
vendored
|
@ -39,7 +39,7 @@ type DigestAuth struct {
|
||||||
ClientCacheTolerance int
|
ClientCacheTolerance int
|
||||||
|
|
||||||
clients map[string]*digest_client
|
clients map[string]*digest_client
|
||||||
mutex sync.Mutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that DigestAuth implements AuthenticatorInterface
|
// check that DigestAuth implements AuthenticatorInterface
|
||||||
|
@ -84,11 +84,16 @@ func (a *DigestAuth) Purge(count int) {
|
||||||
(or requires reauthentication).
|
(or requires reauthentication).
|
||||||
*/
|
*/
|
||||||
func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
|
func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
|
a.mutex.Lock()
|
||||||
|
defer a.mutex.Unlock()
|
||||||
if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
|
if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
|
||||||
a.Purge(a.ClientCacheTolerance * 2)
|
a.Purge(a.ClientCacheTolerance * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := RandomKey()
|
nonce := RandomKey()
|
||||||
|
|
||||||
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||||
|
|
||||||
w.Header().Set(contentType, a.Headers.V().UnauthContentType)
|
w.Header().Set(contentType, a.Headers.V().UnauthContentType)
|
||||||
w.Header().Set(a.Headers.V().Authenticate,
|
w.Header().Set(a.Headers.V().Authenticate,
|
||||||
fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
|
fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
|
||||||
|
@ -118,8 +123,8 @@ func DigestAuthParams(authorization string) map[string]string {
|
||||||
Authentication-Info response header.
|
Authentication-Info response header.
|
||||||
*/
|
*/
|
||||||
func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) {
|
func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) {
|
||||||
da.mutex.Lock()
|
da.mutex.RLock()
|
||||||
defer da.mutex.Unlock()
|
defer da.mutex.RUnlock()
|
||||||
username = ""
|
username = ""
|
||||||
authinfo = nil
|
authinfo = nil
|
||||||
auth := DigestAuthParams(r.Header.Get(da.Headers.V().Authorization))
|
auth := DigestAuthParams(r.Header.Get(da.Headers.V().Authorization))
|
||||||
|
@ -242,6 +247,8 @@ func (a *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc {
|
||||||
|
|
||||||
// NewContext returns a context carrying authentication information for the request.
|
// NewContext returns a context carrying authentication information for the request.
|
||||||
func (a *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
|
func (a *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
|
||||||
|
a.mutex.Lock()
|
||||||
|
defer a.mutex.Unlock()
|
||||||
username, authinfo := a.CheckAuth(r)
|
username, authinfo := a.CheckAuth(r)
|
||||||
info := &Info{Username: username, ResponseHeaders: make(http.Header)}
|
info := &Info{Username: username, ResponseHeaders: make(http.Header)}
|
||||||
if username != "" {
|
if username != "" {
|
||||||
|
|
Loading…
Reference in a new issue