Merge 'v1.6.6' into v1.7
This commit is contained in:
commit
bd3b787fd5
13 changed files with 220 additions and 26 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,5 +1,23 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## [v1.6.6](https://github.com/containous/traefik/tree/v1.6.6) (2018-08-20)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v1.6.5...v1.6.6)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[acme]** Avoid duplicated ACME resolution ([#3751](https://github.com/containous/traefik/pull/3751) by [nmengin](https://github.com/nmengin))
|
||||||
|
- **[api]** Remove TLS in API ([#3788](https://github.com/containous/traefik/pull/3788) by [Juliens](https://github.com/Juliens))
|
||||||
|
- **[cluster]** Remove unusable `--cluster` flag ([#3616](https://github.com/containous/traefik/pull/3616) by [dtomcej](https://github.com/dtomcej))
|
||||||
|
- **[ecs]** Fix bad condition in ECS provider ([#3609](https://github.com/containous/traefik/pull/3609) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Set keepalive on TCP socket so idleTimeout works ([#3740](https://github.com/containous/traefik/pull/3740) by [ajardan](https://github.com/ajardan))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- A tiny rewording on the documentation API's page ([#3794](https://github.com/containous/traefik/pull/3794) by [dduportal](https://github.com/dduportal))
|
||||||
|
- Adding warnings and solution about the configuration exposure ([#3790](https://github.com/containous/traefik/pull/3790) by [dduportal](https://github.com/dduportal))
|
||||||
|
- Fix path to the debug pprof API ([#3608](https://github.com/containous/traefik/pull/3608) by [multani](https://github.com/multani))
|
||||||
|
|
||||||
|
**Misc:**
|
||||||
|
- **[oxy,websocket]** Update oxy dependency ([#3777](https://github.com/containous/traefik/pull/3777) by [Juliens](https://github.com/Juliens))
|
||||||
|
|
||||||
## [v1.7.0-rc3](https://github.com/containous/traefik/tree/v1.7.0-rc3) (2018-08-01)
|
## [v1.7.0-rc3](https://github.com/containous/traefik/tree/v1.7.0-rc3) (2018-08-01)
|
||||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.0-rc2...v1.7.0-rc3)
|
[All Commits](https://github.com/containous/traefik/compare/v1.7.0-rc2...v1.7.0-rc3)
|
||||||
|
|
||||||
|
|
4
Gopkg.lock
generated
4
Gopkg.lock
generated
|
@ -1272,7 +1272,7 @@
|
||||||
"roundrobin",
|
"roundrobin",
|
||||||
"utils"
|
"utils"
|
||||||
]
|
]
|
||||||
revision = "fb889e801a26e7e18ef36322ac72a07157f8cc1f"
|
revision = "885e42fe04d8e0efa6c18facad4e0fc5757cde9b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/vulcand/predicate"
|
name = "github.com/vulcand/predicate"
|
||||||
|
@ -1762,6 +1762,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "2b7ffb1d01d8a14224fcc9964900fb5a39fbf38cfacba45f49b931136e4fee9b"
|
inputs-digest = "b75bf0ae5b8c1ae1ba578fe5a58dfc4cd4270e02f5ea3b9f0d5a92972a36e9b2"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
39
acme/acme.go
39
acme/acme.go
|
@ -12,6 +12,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
|
@ -64,6 +65,8 @@ type ACME struct {
|
||||||
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
|
||||||
|
resolvingDomains map[string]struct{}
|
||||||
|
resolvingDomainsMutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) init() error {
|
func (a *ACME) init() error {
|
||||||
|
@ -76,6 +79,10 @@ func (a *ACME) init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
a.jobs = channels.NewInfiniteChannel()
|
a.jobs = channels.NewInfiniteChannel()
|
||||||
|
|
||||||
|
// Init the currently resolved domain map
|
||||||
|
a.resolvingDomains = make(map[string]struct{})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,6 +544,10 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||||
if len(uncheckedDomains) == 0 {
|
if len(uncheckedDomains) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.addResolvingDomains(uncheckedDomains)
|
||||||
|
defer a.removeResolvingDomains(uncheckedDomains)
|
||||||
|
|
||||||
certificate, err := a.getDomainsCertificates(uncheckedDomains)
|
certificate, err := a.getDomainsCertificates(uncheckedDomains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error getting ACME certificates %+v : %v", uncheckedDomains, err)
|
log.Errorf("Error getting ACME certificates %+v : %v", uncheckedDomains, err)
|
||||||
|
@ -568,6 +579,24 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ACME) addResolvingDomains(resolvingDomains []string) {
|
||||||
|
a.resolvingDomainsMutex.Lock()
|
||||||
|
defer a.resolvingDomainsMutex.Unlock()
|
||||||
|
|
||||||
|
for _, domain := range resolvingDomains {
|
||||||
|
a.resolvingDomains[domain] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACME) removeResolvingDomains(resolvingDomains []string) {
|
||||||
|
a.resolvingDomainsMutex.Lock()
|
||||||
|
defer a.resolvingDomainsMutex.Unlock()
|
||||||
|
|
||||||
|
for _, domain := range resolvingDomains {
|
||||||
|
delete(a.resolvingDomains, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
@ -603,6 +632,9 @@ func searchProvidedCertificateForDomains(domain string, certs map[string]*tls.Ce
|
||||||
// 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) getUncheckedDomains(domains []string, account *Account) []string {
|
func (a *ACME) getUncheckedDomains(domains []string, account *Account) []string {
|
||||||
|
a.resolvingDomainsMutex.RLock()
|
||||||
|
defer a.resolvingDomainsMutex.RUnlock()
|
||||||
|
|
||||||
log.Debugf("Looking for provided certificate to validate %s...", domains)
|
log.Debugf("Looking for provided certificate to validate %s...", domains)
|
||||||
allCerts := make(map[string]*tls.Certificate)
|
allCerts := make(map[string]*tls.Certificate)
|
||||||
|
|
||||||
|
@ -625,6 +657,13 @@ func (a *ACME) getUncheckedDomains(domains []string, account *Account) []string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get currently resolved domains
|
||||||
|
for domain := range a.resolvingDomains {
|
||||||
|
if _, ok := allCerts[domain]; !ok {
|
||||||
|
allCerts[domain] = &tls.Certificate{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get Configuration Domains
|
// Get Configuration Domains
|
||||||
for i := 0; i < len(a.Domains); i++ {
|
for i := 0; i < len(a.Domains); i++ {
|
||||||
allCerts[a.Domains[i].Main] = &tls.Certificate{}
|
allCerts[a.Domains[i].Main] = &tls.Certificate{}
|
||||||
|
|
|
@ -331,9 +331,12 @@ func TestAcme_getUncheckedCertificates(t *testing.T) {
|
||||||
mm["*.containo.us"] = &tls.Certificate{}
|
mm["*.containo.us"] = &tls.Certificate{}
|
||||||
mm["traefik.acme.io"] = &tls.Certificate{}
|
mm["traefik.acme.io"] = &tls.Certificate{}
|
||||||
|
|
||||||
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}}
|
dm := make(map[string]struct{})
|
||||||
|
dm["*.traefik.wtf"] = struct{}{}
|
||||||
|
|
||||||
domains := []string{"traefik.containo.us", "trae.containo.us"}
|
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}, resolvingDomains: dm}
|
||||||
|
|
||||||
|
domains := []string{"traefik.containo.us", "trae.containo.us", "foo.traefik.wtf"}
|
||||||
uncheckedDomains := a.getUncheckedDomains(domains, nil)
|
uncheckedDomains := a.getUncheckedDomains(domains, nil)
|
||||||
assert.Empty(t, uncheckedDomains)
|
assert.Empty(t, uncheckedDomains)
|
||||||
domains = []string{"traefik.acme.io", "trae.acme.io"}
|
domains = []string{"traefik.acme.io", "trae.acme.io"}
|
||||||
|
@ -351,6 +354,9 @@ func TestAcme_getUncheckedCertificates(t *testing.T) {
|
||||||
account := Account{DomainsCertificate: domainsCertificates}
|
account := Account{DomainsCertificate: domainsCertificates}
|
||||||
uncheckedDomains = a.getUncheckedDomains(domains, &account)
|
uncheckedDomains = a.getUncheckedDomains(domains, &account)
|
||||||
assert.Empty(t, uncheckedDomains)
|
assert.Empty(t, uncheckedDomains)
|
||||||
|
domains = []string{"traefik.containo.us", "trae.containo.us", "traefik.wtf"}
|
||||||
|
uncheckedDomains = a.getUncheckedDomains(domains, nil)
|
||||||
|
assert.Len(t, uncheckedDomains, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAcme_getProvidedCertificate(t *testing.T) {
|
func TestAcme_getProvidedCertificate(t *testing.T) {
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# API definition
|
# API definition
|
||||||
|
# Warning: Enabling API will expose Træfik's configuration.
|
||||||
|
# It is not recommended in production,
|
||||||
|
# unless secured by authentication and authorizations
|
||||||
[api]
|
[api]
|
||||||
# Name of the related entry point
|
# Name of the related entry point
|
||||||
#
|
#
|
||||||
|
@ -12,7 +15,7 @@
|
||||||
#
|
#
|
||||||
entryPoint = "traefik"
|
entryPoint = "traefik"
|
||||||
|
|
||||||
# Enabled Dashboard
|
# Enable Dashboard
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
# Default: true
|
# Default: true
|
||||||
|
@ -38,6 +41,22 @@ For more customization, see [entry points](/configuration/entrypoints/) document
|
||||||
|
|
||||||
![Web UI Health](/img/traefik-health.png)
|
![Web UI Health](/img/traefik-health.png)
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Enabling the API will expose all configuration elements,
|
||||||
|
including sensitive data.
|
||||||
|
|
||||||
|
It is not recommended in production,
|
||||||
|
unless secured by authentication and authorizations.
|
||||||
|
|
||||||
|
A good sane default (but not exhaustive) set of recommendations
|
||||||
|
would be to apply the following protection mechanism:
|
||||||
|
|
||||||
|
* _At application level:_ enabling HTTP [Basic Authentication](#authentication)
|
||||||
|
* _At transport level:_ NOT exposing publicly the API's port,
|
||||||
|
keeping it restricted over internal networks
|
||||||
|
(restricted networks as in https://en.wikipedia.org/wiki/Principle_of_least_privilege).
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
| Path | Method | Description |
|
| Path | Method | Description |
|
||||||
|
|
|
@ -86,6 +86,10 @@ services:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
|
- /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
Enabling the Web UI with the `--api` flag might exposes configuration elements. You can read more about this on the [API/Dashboard's Security section](/configuration/api#security).
|
||||||
|
|
||||||
|
|
||||||
**That's it. Now you can launch Træfik!**
|
**That's it. Now you can launch Træfik!**
|
||||||
|
|
||||||
Start your `reverse-proxy` with the following command:
|
Start your `reverse-proxy` with the following command:
|
||||||
|
@ -199,3 +203,8 @@ Using the tiny Docker image:
|
||||||
```shell
|
```shell
|
||||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
We want to keep Træfik safe for everyone.
|
||||||
|
If you've discovered a security vulnerability in Træfik, we appreciate your help in disclosing it to us in a responsible manner, using [this form](https://security.traefik.io).
|
|
@ -8,7 +8,7 @@ In addition, we want to use Let's Encrypt to automatically generate and renew SS
|
||||||
|
|
||||||
## Setting Up
|
## Setting Up
|
||||||
|
|
||||||
In order for this to work, you'll need a server with a public IP address, with Docker installed on it.
|
In order for this to work, you'll need a server with a public IP address, with Docker and docker-compose installed on it.
|
||||||
|
|
||||||
In this example, we're using the fictitious domain _my-awesome-app.org_.
|
In this example, we're using the fictitious domain _my-awesome-app.org_.
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ start_boulder() {
|
||||||
# Script usage
|
# Script usage
|
||||||
show_usage() {
|
show_usage() {
|
||||||
echo
|
echo
|
||||||
echo "USAGE : manage_acme_docker_environment.sh [--start|--stop|--restart]"
|
echo "USAGE : manage_acme_docker_environment.sh [--dev|--start|--stop|--restart]"
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,14 +16,11 @@ theme:
|
||||||
include_sidebar: true
|
include_sidebar: true
|
||||||
favicon: img/traefik.icon.png
|
favicon: img/traefik.icon.png
|
||||||
logo: img/traefik.logo.png
|
logo: img/traefik.logo.png
|
||||||
palette:
|
|
||||||
primary: 'blue'
|
|
||||||
accent: 'light blue'
|
|
||||||
feature:
|
|
||||||
tabs: false
|
|
||||||
palette:
|
palette:
|
||||||
primary: 'cyan'
|
primary: 'cyan'
|
||||||
accent: 'cyan'
|
accent: 'cyan'
|
||||||
|
feature:
|
||||||
|
tabs: false
|
||||||
i18n:
|
i18n:
|
||||||
prev: 'Previous'
|
prev: 'Previous'
|
||||||
next: 'Next'
|
next: 'Next'
|
||||||
|
|
|
@ -62,6 +62,8 @@ type Provider struct {
|
||||||
clientMutex sync.Mutex
|
clientMutex sync.Mutex
|
||||||
configFromListenerChan chan types.Configuration
|
configFromListenerChan chan types.Configuration
|
||||||
pool *safe.Pool
|
pool *safe.Pool
|
||||||
|
resolvingDomains map[string]struct{}
|
||||||
|
resolvingDomainsMutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Certificate is a struct which contains all data needed from an ACME certificate
|
// Certificate is a struct which contains all data needed from an ACME certificate
|
||||||
|
@ -144,6 +146,9 @@ func (p *Provider) Init(_ types.Constraints) error {
|
||||||
return fmt.Errorf("unable to get ACME certificates : %v", err)
|
return fmt.Errorf("unable to get ACME certificates : %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init the currently resolved domain map
|
||||||
|
p.resolvingDomains = make(map[string]struct{})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,6 +378,9 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.addResolvingDomains(uncheckedDomains)
|
||||||
|
defer p.removeResolvingDomains(uncheckedDomains)
|
||||||
|
|
||||||
log.Debugf("Loading ACME certificates %+v...", uncheckedDomains)
|
log.Debugf("Loading ACME certificates %+v...", uncheckedDomains)
|
||||||
|
|
||||||
client, err := p.getClient()
|
client, err := p.getClient()
|
||||||
|
@ -410,6 +418,24 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
|
||||||
return certificate, nil
|
return certificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) removeResolvingDomains(resolvingDomains []string) {
|
||||||
|
p.resolvingDomainsMutex.Lock()
|
||||||
|
defer p.resolvingDomainsMutex.Unlock()
|
||||||
|
|
||||||
|
for _, domain := range resolvingDomains {
|
||||||
|
delete(p.resolvingDomains, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) addResolvingDomains(resolvingDomains []string) {
|
||||||
|
p.resolvingDomainsMutex.Lock()
|
||||||
|
defer p.resolvingDomainsMutex.Unlock()
|
||||||
|
|
||||||
|
for _, domain := range resolvingDomains {
|
||||||
|
p.resolvingDomains[domain] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) useCertificateWithRetry(domains []string) bool {
|
func (p *Provider) useCertificateWithRetry(domains []string) bool {
|
||||||
// Check if we can use the retry mechanism only if we use the DNS Challenge and if is there are at least 2 domains to check
|
// Check if we can use the retry mechanism only if we use the DNS Challenge and if is there are at least 2 domains to check
|
||||||
if p.DNSChallenge != nil && len(domains) > 1 {
|
if p.DNSChallenge != nil && len(domains) > 1 {
|
||||||
|
@ -636,6 +662,9 @@ func (p *Provider) renewCertificates() {
|
||||||
// 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 (p *Provider) getUncheckedDomains(domainsToCheck []string, checkConfigurationDomains bool) []string {
|
func (p *Provider) getUncheckedDomains(domainsToCheck []string, checkConfigurationDomains bool) []string {
|
||||||
|
p.resolvingDomainsMutex.RLock()
|
||||||
|
defer p.resolvingDomainsMutex.RUnlock()
|
||||||
|
|
||||||
log.Debugf("Looking for provided certificate(s) to validate %q...", domainsToCheck)
|
log.Debugf("Looking for provided certificate(s) to validate %q...", domainsToCheck)
|
||||||
|
|
||||||
allDomains := p.certificateStore.GetAllDomains()
|
allDomains := p.certificateStore.GetAllDomains()
|
||||||
|
@ -645,6 +674,11 @@ func (p *Provider) getUncheckedDomains(domainsToCheck []string, checkConfigurati
|
||||||
allDomains = append(allDomains, strings.Join(certificate.Domain.ToStrArray(), ","))
|
allDomains = append(allDomains, strings.Join(certificate.Domain.ToStrArray(), ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get currently resolved domains
|
||||||
|
for domain := range p.resolvingDomains {
|
||||||
|
allDomains = append(allDomains, domain)
|
||||||
|
}
|
||||||
|
|
||||||
// Get Configuration Domains
|
// Get Configuration Domains
|
||||||
if checkConfigurationDomains {
|
if checkConfigurationDomains {
|
||||||
for i := 0; i < len(p.Domains); i++ {
|
for i := 0; i < len(p.Domains); i++ {
|
||||||
|
@ -664,7 +698,7 @@ func searchUncheckedDomains(domainsToCheck []string, existentDomains []string) [
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(uncheckedDomains) == 0 {
|
if len(uncheckedDomains) == 0 {
|
||||||
log.Debugf("No ACME certificate to generate for domains %q.", domainsToCheck)
|
log.Debugf("No ACME certificate generation required for domains %q.", domainsToCheck)
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("Domains %q need ACME certificates generation for domains %q.", domainsToCheck, strings.Join(uncheckedDomains, ","))
|
log.Debugf("Domains %q need ACME certificates generation for domains %q.", domainsToCheck, strings.Join(uncheckedDomains, ","))
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
||||||
desc string
|
desc string
|
||||||
dynamicCerts *safe.Safe
|
dynamicCerts *safe.Safe
|
||||||
staticCerts *safe.Safe
|
staticCerts *safe.Safe
|
||||||
|
resolvingDomains map[string]struct{}
|
||||||
acmeCertificates []*Certificate
|
acmeCertificates []*Certificate
|
||||||
domains []string
|
domains []string
|
||||||
expectedDomains []string
|
expectedDomains []string
|
||||||
|
@ -140,6 +141,40 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedDomains: []string{"traefik.wtf"},
|
expectedDomains: []string{"traefik.wtf"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "all domains already managed by ACME",
|
||||||
|
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||||
|
resolvingDomains: map[string]struct{}{
|
||||||
|
"traefik.wtf": {},
|
||||||
|
"foo.traefik.wtf": {},
|
||||||
|
},
|
||||||
|
expectedDomains: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "one domain already managed by ACME",
|
||||||
|
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||||
|
resolvingDomains: map[string]struct{}{
|
||||||
|
"traefik.wtf": {},
|
||||||
|
},
|
||||||
|
expectedDomains: []string{"foo.traefik.wtf"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "wildcard domain already managed by ACME checks the domains",
|
||||||
|
domains: []string{"bar.traefik.wtf", "foo.traefik.wtf"},
|
||||||
|
resolvingDomains: map[string]struct{}{
|
||||||
|
"*.traefik.wtf": {},
|
||||||
|
},
|
||||||
|
expectedDomains: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "wildcard domain already managed by ACME checks domains and another domain checks one other domain, one domain still unchecked",
|
||||||
|
domains: []string{"traefik.wtf", "bar.traefik.wtf", "foo.traefik.wtf", "acme.wtf"},
|
||||||
|
resolvingDomains: map[string]struct{}{
|
||||||
|
"*.traefik.wtf": {},
|
||||||
|
"traefik.wtf": {},
|
||||||
|
},
|
||||||
|
expectedDomains: []string{"acme.wtf"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
@ -147,12 +182,17 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
if test.resolvingDomains == nil {
|
||||||
|
test.resolvingDomains = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
acmeProvider := Provider{
|
acmeProvider := Provider{
|
||||||
certificateStore: &traefiktls.CertificateStore{
|
certificateStore: &traefiktls.CertificateStore{
|
||||||
DynamicCerts: test.dynamicCerts,
|
DynamicCerts: test.dynamicCerts,
|
||||||
StaticCerts: test.staticCerts,
|
StaticCerts: test.staticCerts,
|
||||||
},
|
},
|
||||||
certificates: test.acmeCertificates,
|
certificates: test.acmeCertificates,
|
||||||
|
resolvingDomains: test.resolvingDomains,
|
||||||
}
|
}
|
||||||
|
|
||||||
domains := acmeProvider.getUncheckedDomains(test.domains, false)
|
domains := acmeProvider.getUncheckedDomains(test.domains, false)
|
||||||
|
|
34
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
34
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
|
@ -4,9 +4,11 @@
|
||||||
package forward
|
package forward
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -309,11 +311,6 @@ func (f *httpForwarder) modifyRequest(outReq *http.Request, target *url.URL) {
|
||||||
outReq.URL.RawQuery = u.RawQuery
|
outReq.URL.RawQuery = u.RawQuery
|
||||||
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||||
|
|
||||||
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
|
||||||
if !f.passHost {
|
|
||||||
outReq.Host = target.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
outReq.Proto = "HTTP/1.1"
|
outReq.Proto = "HTTP/1.1"
|
||||||
outReq.ProtoMajor = 1
|
outReq.ProtoMajor = 1
|
||||||
outReq.ProtoMinor = 1
|
outReq.ProtoMinor = 1
|
||||||
|
@ -321,6 +318,11 @@ func (f *httpForwarder) modifyRequest(outReq *http.Request, target *url.URL) {
|
||||||
if f.rewriter != nil {
|
if f.rewriter != nil {
|
||||||
f.rewriter.Rewrite(outReq)
|
f.rewriter.Rewrite(outReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
||||||
|
if !f.passHost {
|
||||||
|
outReq.Host = target.Host
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveHTTP forwards websocket traffic
|
// serveHTTP forwards websocket traffic
|
||||||
|
@ -396,16 +398,28 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request,
|
||||||
errBackend := make(chan error, 1)
|
errBackend := make(chan error, 1)
|
||||||
replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) {
|
replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) {
|
||||||
|
|
||||||
|
forward := func(messageType int, reader io.Reader) error {
|
||||||
|
writer, err := dst.NextWriter(messageType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(writer, reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return writer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
src.SetPingHandler(func(data string) error {
|
src.SetPingHandler(func(data string) error {
|
||||||
return dst.WriteMessage(websocket.PingMessage, []byte(data))
|
return forward(websocket.PingMessage, bytes.NewReader([]byte(data)))
|
||||||
})
|
})
|
||||||
|
|
||||||
src.SetPongHandler(func(data string) error {
|
src.SetPongHandler(func(data string) error {
|
||||||
return dst.WriteMessage(websocket.PongMessage, []byte(data))
|
return forward(websocket.PongMessage, bytes.NewReader([]byte(data)))
|
||||||
})
|
})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
msgType, msg, err := src.ReadMessage()
|
msgType, reader, err := src.NextReader()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
|
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
|
||||||
|
@ -423,11 +437,11 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request,
|
||||||
}
|
}
|
||||||
errc <- err
|
errc <- err
|
||||||
if m != nil {
|
if m != nil {
|
||||||
dst.WriteMessage(websocket.CloseMessage, m)
|
forward(websocket.CloseMessage, bytes.NewReader([]byte(m)))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
err = dst.WriteMessage(msgType, msg)
|
err = forward(msgType, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- err
|
errc <- err
|
||||||
break
|
break
|
||||||
|
|
22
vendor/github.com/vulcand/oxy/utils/handler.go
generated
vendored
22
vendor/github.com/vulcand/oxy/utils/handler.go
generated
vendored
|
@ -1,6 +1,7 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -8,6 +9,12 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StatusClientClosedRequest non-standard HTTP status code for client disconnection
|
||||||
|
const StatusClientClosedRequest = 499
|
||||||
|
|
||||||
|
// StatusClientClosedRequestText non-standard HTTP status for client disconnection
|
||||||
|
const StatusClientClosedRequestText = "Client Closed Request"
|
||||||
|
|
||||||
// ErrorHandler error handler
|
// ErrorHandler error handler
|
||||||
type ErrorHandler interface {
|
type ErrorHandler interface {
|
||||||
ServeHTTP(w http.ResponseWriter, req *http.Request, err error)
|
ServeHTTP(w http.ResponseWriter, req *http.Request, err error)
|
||||||
|
@ -21,6 +28,7 @@ type StdHandler struct{}
|
||||||
|
|
||||||
func (e *StdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err error) {
|
func (e *StdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err error) {
|
||||||
statusCode := http.StatusInternalServerError
|
statusCode := http.StatusInternalServerError
|
||||||
|
|
||||||
if e, ok := err.(net.Error); ok {
|
if e, ok := err.(net.Error); ok {
|
||||||
if e.Timeout() {
|
if e.Timeout() {
|
||||||
statusCode = http.StatusGatewayTimeout
|
statusCode = http.StatusGatewayTimeout
|
||||||
|
@ -29,10 +37,20 @@ func (e *StdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err err
|
||||||
}
|
}
|
||||||
} else if err == io.EOF {
|
} else if err == io.EOF {
|
||||||
statusCode = http.StatusBadGateway
|
statusCode = http.StatusBadGateway
|
||||||
|
} else if err == context.Canceled {
|
||||||
|
statusCode = StatusClientClosedRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
w.Write([]byte(http.StatusText(statusCode)))
|
w.Write([]byte(statusText(statusCode)))
|
||||||
log.Debugf("'%d %s' caused by: %v", statusCode, http.StatusText(statusCode), err)
|
log.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusText(statusCode int) string {
|
||||||
|
if statusCode == StatusClientClosedRequest {
|
||||||
|
return StatusClientClosedRequestText
|
||||||
|
}
|
||||||
|
return http.StatusText(statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorHandlerFunc error handler function type
|
// ErrorHandlerFunc error handler function type
|
||||||
|
|
Loading…
Reference in a new issue