Compare commits

...

23 Commits

Author SHA1 Message Date
mpl
74ad83f05a Prepare release v1.7.18 2019-09-26 15:46:05 +02:00
Ludovic Fernandez
d707c8ba93 Prepare release v1.7.17 2019-09-23 19:48:04 +02:00
Nicholas Wiersma
640eb62ca1 Avoid closing stdout when the accesslog handler is closed
Co-authored-by: Ludovic Fernandez <ldez@users.noreply.github.com>
Co-authored-by: jlevesy <julien.levesy@containo.us>
2019-09-23 14:50:06 +02:00
mpl
216710864e Actually send header and code during WriteHeader, if needed
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
2019-09-20 18:42:03 +02:00
Brad Jones
226f20b626 Add note clarifying client certificate header 2019-09-19 09:06:03 +02:00
Ludovic Fernandez
151be83bce Update docs links. 2019-09-18 15:38:04 +02:00
Ludovic Fernandez
d1a8c7fa78 Update Traefik image version. 2019-09-17 14:12:04 +02:00
Ludovic Fernandez
254dc38c3d Prepare release v1.7.16 2019-09-13 15:04:04 +02:00
mpl
24d084d7e6 implement Flusher and Hijacker for codeCatcher 2019-09-13 14:32:03 +02:00
Ludovic Fernandez
df5f530058 Prepare release v1.7.15 2019-09-12 18:10:05 +02:00
mpl
753d173965 error pages: do not buffer response when it's not an error 2019-09-12 16:20:05 +02:00
Daniel Tomcej
ffd1f122de Add TLS minversion constraint 2019-09-12 14:48:05 +02:00
Piotr Majkrzak
f98b57fdf4 Fix wrong handling of insecure tls auth forward ingress annotation 2019-09-12 11:44:05 +02:00
Damien Duportal
2d37f08864 Improve Access Logs Documentation page 2019-09-11 18:14:03 +02:00
Nicholas Wiersma
a7dbcc282c Consider default cert domain in certificate store
Co-authored-by: Nicolas Mengin <nmengin.pro@gmail.com>
2019-09-11 17:46:04 +02:00
David Dymko
f4f62e7fb3 Update Acme doc - Vultr Wildcard & Root 2019-09-09 09:26:04 +02:00
mpl
4cae8bcb10 Finish kubernetes throttling refactoring 2019-08-31 05:10:04 -07:00
Ben Weissmann
bee370ec6b Throttle Kubernetes config refresh 2019-08-30 03:16:04 -07:00
pitan
f1d016b893 Typo in basic auth usersFile label consul-catalog 2019-08-21 01:36:03 -07:00
Erik Wegner
4defbbe848 Kubernetes support for Auth.HeaderField 2019-08-21 01:30:05 -07:00
Ludovic Fernandez
f397342f16 Prepare release v1.7.14 2019-08-14 02:06:03 -07:00
Ludovic Fernandez
989a59cc29 Update to go1.12.8 2019-08-14 01:52:05 -07:00
Julien Levesy
c5b71592c8 Make hijackConnectionTracker.Close thread safe 2019-08-12 02:34:04 -07:00
45 changed files with 706 additions and 202 deletions

20
.semaphoreci/golang.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -e
curl -O https://dl.google.com/go/go1.12.linux-amd64.tar.gz
tar -xvf go1.12.linux-amd64.tar.gz
rm -rf go1.12.linux-amd64.tar.gz
sudo mkdir -p /usr/local/golang/1.12/go
sudo mv go /usr/local/golang/1.12/
sudo rm /usr/local/bin/go
sudo chmod +x /usr/local/golang/1.12/go/bin/go
sudo ln -s /usr/local/golang/1.12/go/bin/go /usr/local/bin/go
export GOROOT="/usr/local/golang/1.12/go"
export GOTOOLDIR="/usr/local/golang/1.12/go/pkg/tool/linux_amd64"
go version

View File

@@ -13,6 +13,7 @@ env:
- VERSION: $TRAVIS_TAG
- CODENAME: maroilles
- N_MAKE_JOBS: 2
- DOCS_VERIFY_SKIP: true
script:
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"

View File

@@ -1,5 +1,53 @@
# Change Log
## [v1.7.18](https://github.com/containous/traefik/tree/v1.7.18) (2019-09-23)
[All Commits](https://github.com/containous/traefik/compare/v1.7.17...v1.7.18)
**Bug fixes:**
- **[go,security]** This version is compiled with [Go 1.12.10](https://groups.google.com/d/msg/golang-announce/cszieYyuL9Q/g4Z7pKaqAgAJ), which fixes a vulnerability in previous versions. See the [CVE](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16276) about it for more details.
## [v1.7.17](https://github.com/containous/traefik/tree/v1.7.17) (2019-09-23)
[All Commits](https://github.com/containous/traefik/compare/v1.7.16...v1.7.17)
**Bug fixes:**
- **[logs,middleware]** Avoid closing stdout when the accesslog handler is closed ([#5459](https://github.com/containous/traefik/pull/5459) by [nrwiersma](https://github.com/nrwiersma))
- **[middleware]** Actually send header and code during WriteHeader, if needed ([#5404](https://github.com/containous/traefik/pull/5404) by [mpl](https://github.com/mpl))
**Documentation:**
- **[k8s]** Add note clarifying client certificate header ([#5362](https://github.com/containous/traefik/pull/5362) by [bradjones1](https://github.com/bradjones1))
- **[webui]** Update docs links. ([#5412](https://github.com/containous/traefik/pull/5412) by [ldez](https://github.com/ldez))
- Update Traefik image version. ([#5399](https://github.com/containous/traefik/pull/5399) by [ldez](https://github.com/ldez))
## [v1.7.16](https://github.com/containous/traefik/tree/v1.7.16) (2019-09-13)
[All Commits](https://github.com/containous/traefik/compare/v1.7.15...v1.7.16)
**Bug fixes:**
- **[middleware,websocket]** implement Flusher and Hijacker for codeCatcher ([#5376](https://github.com/containous/traefik/pull/5376) by [mpl](https://github.com/mpl))
## [v1.7.15](https://github.com/containous/traefik/tree/v1.7.15) (2019-09-12)
[All Commits](https://github.com/containous/traefik/compare/v1.7.14...v1.7.15)
**Bug fixes:**
- **[authentication,k8s/ingress]** Kubernetes support for Auth.HeaderField ([#5235](https://github.com/containous/traefik/pull/5235) by [ErikWegner](https://github.com/ErikWegner))
- **[k8s,k8s/ingress]** Finish kubernetes throttling refactoring ([#5269](https://github.com/containous/traefik/pull/5269) by [mpl](https://github.com/mpl))
- **[k8s]** Throttle Kubernetes config refresh ([#4716](https://github.com/containous/traefik/pull/4716) by [benweissmann](https://github.com/benweissmann))
- **[k8s]** Fix wrong handling of insecure tls auth forward ingress annotation ([#5319](https://github.com/containous/traefik/pull/5319) by [majkrzak](https://github.com/majkrzak))
- **[middleware]** error pages: do not buffer response when it&#39;s not an error ([#5285](https://github.com/containous/traefik/pull/5285) by [mpl](https://github.com/mpl))
- **[tls]** Consider default cert domain in certificate store ([#5353](https://github.com/containous/traefik/pull/5353) by [nrwiersma](https://github.com/nrwiersma))
- **[tls]** Add TLS minversion constraint ([#5356](https://github.com/containous/traefik/pull/5356) by [dtomcej](https://github.com/dtomcej))
**Documentation:**
- **[acme]** Update Acme doc - Vultr Wildcard &amp; Root ([#5320](https://github.com/containous/traefik/pull/5320) by [ddymko](https://github.com/ddymko))
- **[consulcatalog]** Typo in basic auth usersFile label consul-catalog ([#5230](https://github.com/containous/traefik/pull/5230) by [pitan](https://github.com/pitan))
- **[logs]** Improve Access Logs Documentation page ([#5238](https://github.com/containous/traefik/pull/5238) by [dduportal](https://github.com/dduportal))
## [v1.7.14](https://github.com/containous/traefik/tree/v1.7.14) (2019-08-14)
[All Commits](https://github.com/containous/traefik/compare/v1.7.13...v1.7.14)
**Bug fixes:**
- Update to go1.12.8 ([#5201](https://github.com/containous/traefik/pull/5201) by [ldez](https://github.com/ldez)). HTTP/2 Denial of Service [CVE-2019-9512](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9512) and [CVE-2019-9514](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9514)
- **[server]** Make hijackConnectionTracker.Close thread safe ([#5194](https://github.com/containous/traefik/pull/5194) by [jlevesy](https://github.com/jlevesy))
## [v1.7.13](https://github.com/containous/traefik/tree/v1.7.13) (2019-08-07)
[All Commits](https://github.com/containous/traefik/compare/v1.7.12...v1.7.13)

View File

@@ -13,7 +13,7 @@ You need to run the `binary` target. This will create binaries for Linux platfor
$ make binary
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
Sending build context to Docker daemon 295.3 MB
Step 0 : FROM golang:1.11-alpine
Step 0 : FROM golang:1.12-alpine
---> 8c6473912976
Step 1 : RUN go get github.com/golang/dep/cmd/dep
[...]
@@ -158,7 +158,7 @@ Integration tests must be run from the `integration/` directory and require the
## Documentation
The [documentation site](https://docs.traefik.io/) is built with [mkdocs](https://mkdocs.org/)
The [documentation site](https://docs.traefik.io/v1.7/) is built with [mkdocs](https://mkdocs.org/)
### Building Documentation

View File

@@ -76,7 +76,7 @@ test-integration: build ## run the integration tests
TEST_HOST=1 ./script/make.sh test-integration
validate: build ## validate code, vendor and autogen
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint validate-misspell validate-vendor validate-autogen
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-golint validate-misspell validate-vendor validate-autogen
build: dist
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .

View File

@@ -4,7 +4,7 @@
</p>
[![Build Status SemaphoreCI](https://semaphoreci.com/api/v1/containous/traefik/branches/master/shields_badge.svg)](https://semaphoreci.com/containous/traefik)
[![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://docs.traefik.io)
[![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://docs.traefik.io/v1.7)
[![Go Report Card](https://goreportcard.com/badge/containous/traefik)](http://goreportcard.com/report/containous/traefik)
[![](https://images.microbadger.com/badges/image/traefik.svg)](https://microbadger.com/images/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/containous/traefik/blob/master/LICENSE.md)
@@ -70,22 +70,22 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
## Supported Backends
- [Docker](https://docs.traefik.io/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/configuration/backends/docker#docker-swarm-mode)
- [Kubernetes](https://docs.traefik.io/configuration/backends/kubernetes)
- [Mesos](https://docs.traefik.io/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/configuration/backends/marathon)
- [Rancher](https://docs.traefik.io/configuration/backends/rancher) (API, Metadata)
- [Azure Service Fabric](https://docs.traefik.io/configuration/backends/servicefabric)
- [Consul Catalog](https://docs.traefik.io/configuration/backends/consulcatalog)
- [Consul](https://docs.traefik.io/configuration/backends/consul) / [Etcd](https://docs.traefik.io/configuration/backends/etcd) / [Zookeeper](https://docs.traefik.io/configuration/backends/zookeeper) / [BoltDB](https://docs.traefik.io/configuration/backends/boltdb)
- [Eureka](https://docs.traefik.io/configuration/backends/eureka)
- [Amazon ECS](https://docs.traefik.io/configuration/backends/ecs)
- [Amazon DynamoDB](https://docs.traefik.io/configuration/backends/dynamodb)
- [File](https://docs.traefik.io/configuration/backends/file)
- [Rest](https://docs.traefik.io/configuration/backends/rest)
- [Docker](https://docs.traefik.io/v1.7/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/v1.7/configuration/backends/docker#docker-swarm-mode)
- [Kubernetes](https://docs.traefik.io/v1.7/configuration/backends/kubernetes)
- [Mesos](https://docs.traefik.io/v1.7/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/v1.7/configuration/backends/marathon)
- [Rancher](https://docs.traefik.io/v1.7/configuration/backends/rancher) (API, Metadata)
- [Azure Service Fabric](https://docs.traefik.io/v1.7/configuration/backends/servicefabric)
- [Consul Catalog](https://docs.traefik.io/v1.7/configuration/backends/consulcatalog)
- [Consul](https://docs.traefik.io/v1.7/configuration/backends/consul) / [Etcd](https://docs.traefik.io/v1.7/configuration/backends/etcd) / [Zookeeper](https://docs.traefik.io/v1.7/configuration/backends/zookeeper) / [BoltDB](https://docs.traefik.io/v1.7/configuration/backends/boltdb)
- [Eureka](https://docs.traefik.io/v1.7/configuration/backends/eureka)
- [Amazon ECS](https://docs.traefik.io/v1.7/configuration/backends/ecs)
- [Amazon DynamoDB](https://docs.traefik.io/v1.7/configuration/backends/dynamodb)
- [File](https://docs.traefik.io/v1.7/configuration/backends/file)
- [Rest](https://docs.traefik.io/v1.7/configuration/backends/rest)
## Quickstart
To get your hands on Traefik, you can use the [5-Minute Quickstart](http://docs.traefik.io/#the-traefik-quickstart-using-docker) in our documentation (you will need Docker).
To get your hands on Traefik, you can use the [5-Minute Quickstart](http://docs.traefik.io/v1.7/#the-traefik-quickstart-using-docker) in our documentation (you will need Docker).
Alternatively, if you don't want to install anything on your computer, you can try Traefik online in this great [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
@@ -100,7 +100,7 @@ You can access the simple HTML frontend of Traefik.
## Documentation
You can find the complete documentation at [https://docs.traefik.io](https://docs.traefik.io).
You can find the complete documentation at [https://docs.traefik.io/v1.7](https://docs.traefik.io/v1.7).
A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
## Support

View File

@@ -1365,7 +1365,9 @@ var _templatesKubernetesTmpl = []byte(`[backends]
{{if $frontend.Auth }}
[frontends."{{ $frontendName }}".auth]
headerField = "X-WebAuth-User"
{{if $frontend.Auth.HeaderField }}
headerField = "{{ $frontend.Auth.HeaderField }}"
{{end}}
{{if $frontend.Auth.Basic }}
[frontends."{{ $frontendName }}".auth.basic]

View File

@@ -1,4 +1,4 @@
FROM golang:1.11-alpine
FROM golang:1.12-alpine
RUN apk --update upgrade \
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \
@@ -6,7 +6,6 @@ RUN apk --update upgrade \
&& rm -rf /var/cache/apk/*
RUN go get golang.org/x/lint/golint \
&& go get github.com/kisielk/errcheck \
&& go get github.com/client9/misspell/cmd/misspell
# Which docker version to test on

View File

@@ -352,14 +352,14 @@ func stats(globalConfiguration *configuration.GlobalConfiguration) {
Stats collection is enabled.
Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.
Help us improve Traefik by leaving this feature on :)
More details on: https://docs.traefik.io/basics/#collected-data
More details on: https://docs.traefik.io/v1.7/basics/#collected-data
`)
collect(globalConfiguration)
} else {
log.Info(`
Stats collection is disabled.
Help us improve Traefik by turning this feature on :)
More details on: https://docs.traefik.io/basics/#collected-data
More details on: https://docs.traefik.io/v1.7/basics/#collected-data
`)
}
}

View File

@@ -212,6 +212,12 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
}
}
// Thanks to SSLv3 being enabled by mistake in golang 1.12,
// If no minVersion is set, apply TLS1.0 as the minimum.
if entryPoint.TLS != nil && len(entryPoint.TLS.MinVersion) == 0 {
entryPoint.TLS.MinVersion = "VersionTLS10"
}
if entryPoint.TLS != nil && entryPoint.TLS.DefaultCertificate == nil && len(entryPoint.TLS.Certificates) > 0 {
log.Infof("No tls.defaultCertificate given for %s: using the first item in tls.certificates as a fallback.", entryPointName)
entryPoint.TLS.DefaultCertificate = &entryPoint.TLS.Certificates[0]

View File

@@ -12,6 +12,7 @@ import (
"github.com/containous/traefik/provider"
acmeprovider "github.com/containous/traefik/provider/acme"
"github.com/containous/traefik/provider/file"
"github.com/containous/traefik/tls"
"github.com/stretchr/testify/assert"
)
@@ -269,3 +270,69 @@ func TestInitACMEProvider(t *testing.T) {
})
}
}
func TestSetEffectiveConfigurationTLSMinVersion(t *testing.T) {
testCases := []struct {
desc string
provided EntryPoint
expected EntryPoint
}{
{
desc: "Entrypoint with no TLS",
provided: EntryPoint{
Address: ":80",
},
expected: EntryPoint{
Address: ":80",
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
},
},
{
desc: "Entrypoint with TLS Specifying MinVersion",
provided: EntryPoint{
Address: ":443",
TLS: &tls.TLS{
MinVersion: "VersionTLS12",
},
},
expected: EntryPoint{
Address: ":443",
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
TLS: &tls.TLS{
MinVersion: "VersionTLS12",
},
},
},
{
desc: "Entrypoint with TLS without Specifying MinVersion",
provided: EntryPoint{
Address: ":443",
TLS: &tls.TLS{},
},
expected: EntryPoint{
Address: ":443",
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
TLS: &tls.TLS{
MinVersion: "VersionTLS10",
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
gc := &GlobalConfiguration{
EntryPoints: map[string]*EntryPoint{
"foo": &test.provided,
},
}
gc.SetEffectiveConfiguration(defaultConfigFile)
assert.Equal(t, &test.expected, gc.EntryPoints["foo"])
})
}
}

View File

@@ -1,6 +1,6 @@
[Unit]
Description=Traefik
Documentation=https://docs.traefik.io
Documentation=https://docs.traefik.io/v1.7
#After=network-online.target
#AssertFileIsExecutable=/usr/bin/traefik
#AssertPathExists=/etc/traefik/traefik.toml

View File

@@ -336,7 +336,7 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | Not tested yet |
| [Versio](https://www.versio.nl/domeinnamen) | `versio` | `VERSIO_USERNAME`, `VERSIO_PASSWORD` | YES |
| [Vscale](https://vscale.io/) | `vscale` | `VSCALE_API_TOKEN` | YES |
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | YES |
| [Zone.ee](https://www.zone.ee) | `zoneee` | `ZONEEE_API_USER`, `ZONEEE_API_KEY` | YES |
- (1): more information about the HTTP message format can be found [here](https://go-acme.github.io/lego/dns/httpreq/)

View File

@@ -130,10 +130,10 @@ Additional settings can be defined using Consul Catalog tags.
| `<prefix>.frontend.auth.basic=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash` (DEPRECATED). |
| `<prefix>.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
| `<prefix>.frontend.auth.basic.users=EXPR` | Sets basic authentication to this frontend in CSV format: `User:Hash,User:Hash`. |
| `<prefix>.frontend.auth.basic.usersfile=/path/.htpasswd` | Sets basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `<prefix>.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `<prefix>.frontend.auth.digest.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
| `<prefix>.frontend.auth.digest.users=EXPR` | Sets digest authentication to this frontend in CSV format: `User:Realm:Hash,User:Realm:Hash`. |
| `<prefix>.frontend.auth.digest.usersfile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `<prefix>.frontend.auth.digest.usersFile=/path/.htdigest` | Sets digest authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
| `<prefix>.frontend.auth.forward.address=https://example.com` | Sets the URL of the authentication server. |
| `<prefix>.frontend.auth.forward.authResponseHeaders=EXPR` | Sets the forward authentication authResponseHeaders in CSV format: `X-Auth-User,X-Auth-Header` |
| `<prefix>.frontend.auth.forward.tls.ca=/path/ca.pem` | Sets the Certificate Authority (CA) for the TLS connection with the authentication server. |

View File

@@ -73,6 +73,14 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
#
# enablePassTLSCert = true
# Throttle how frequently we refresh our configuration from Ingresses when there
# are frequent changes.
#
# Optional
# Default: 0 (no throttling)
#
# throttleDuration = 10s
# Override default configuration template.
#
# Optional
@@ -210,10 +218,14 @@ infos:
serialnumber: true
```
If `pem` is set, it will add a `X-Forwarded-Tls-Client-Cert` header that contains the escaped pem as value.
If `pem` is set, it will add a `X-Forwarded-Tls-Client-Cert` header that contains the escaped pem as value.
If at least one flag of the `infos` part is set, it will add a `X-Forwarded-Tls-Client-Cert-Infos` header that contains an escaped string composed of the client certificate data selected by the infos flags.
This infos part is composed like the following example (not escaped):
```Subject="C=FR,ST=SomeState,L=Lyon,O=Cheese,CN=*.cheese.org",NB=1531900816,NA=1563436816,SAN=*.cheese.org,*.cheese.net,cheese.in,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2```
```
Subject="C=FR,ST=SomeState,L=Lyon,O=Cheese,CN=*.cheese.org",NB=1531900816,NA=1563436816,SAN=*.cheese.org,*.cheese.net,cheese.in,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2
```
Note these options work only with certificates issued by CAs included in the applicable [EntryPoint ClientCA section](/configuration/entrypoints/#tls-mutual-authentication); certificates from other CAs are not parsed or passed through as-is.
<4> `traefik.ingress.kubernetes.io/rate-limit` example:
@@ -231,7 +243,7 @@ rateset:
```
<5> `traefik.ingress.kubernetes.io/rule-type`
Note: `ReplacePath` is deprecated in this annotation, use the `traefik.ingress.kubernetes.io/request-modifier` annotation instead. Default: `PathPrefix`.
Note: `ReplacePath` is deprecated in this annotation, use the `traefik.ingress.kubernetes.io/request-modifier` annotation instead. Default: `PathPrefix`.
<6> `traefik.ingress.kubernetes.io/service-weights`:
Service weights enable to split traffic across multiple backing services in a fine-grained manner.

View File

@@ -97,7 +97,7 @@ In compose file the entrypoint syntax is different. Notice how quotes are used:
```yaml
traefik:
image: traefik
image: traefik:v1.7
command:
- --defaultentrypoints=powpow
- "--entryPoints=Name:powpow Address::42 Compress:true"
@@ -105,7 +105,7 @@ traefik:
or
```yaml
traefik:
image: traefik
image: traefik:v1.7
command: --defaultentrypoints=powpow --entryPoints='Name:powpow Address::42 Compress:true'
```

View File

@@ -105,10 +105,10 @@ logLevel = "ERROR"
## Access Logs
Access logs are written when `[accessLog]` is defined.
By default it will write to stdout and produce logs in the textual [Common Log Format (CLF)](#clf-common-log-format), extended with additional fields.
Access logs are written when the entry `[accessLog]` is defined (or the command line flag `--accesslog`).
By default it writes to stdout and produces logs in the textual [Common Log Format (CLF)](#clf-common-log-format), extended with additional fields.
To enable access logs using the default settings just add the `[accessLog]` entry:
To enable access logs using the default settings, add the `[accessLog]` entry in your `traefik.toml` configuration file:
```toml
[accessLog]
@@ -175,21 +175,41 @@ format = "json" # Default: "common"
minDuration = "10ms"
```
To customize logs format, you must switch to the JSON format:
### CLF - Common Log Format
By default, Traefik use the CLF (`common`) as access log format.
```html
<remote_IP_address> - <client_user_name_if_available> [<timestamp>] "<request_method> <request_path> <request_protocol>" <origin_server_HTTP_status> <origin_server_content_size> "<request_referrer>" "<request_user_agent>" <number_of_requests_received_since_Traefik_started> "<Traefik_frontend_name>" "<Traefik_backend_URL>" <request_duration_in_ms>ms
```
### Customize Fields
You can customize the fields written in the access logs.
The list of available fields is found below: [List of All Available Fields](#list-of-all-available-fields).
Each field has a "mode" which defines if it is written or not in the access log lines.
The possible values for the mode are:
* `keep`: the field and its value are written on the access log line. This is the default behavior.
* `drop`: the field is not written at all on the access log.
To customize the fields, you must:
* Switch to the JSON format (mandatory)
* Define the "default mode" for all fields (default is `keep`)
* OR Define the fields which does not follow the default mode
```toml
[accessLog]
filePath = "/path/to/access.log"
format = "json" # Default: "common"
[accessLog.filters]
# statusCodes keep only access logs with status codes in the specified range
#
# Optional
# Default: []
#
statusCodes = ["200", "300-302"]
# Access Log Format
#
# Optional
# Default: "common"
#
# Accepted values "common", "json"
#
format = "json"
[accessLog.fields]
@@ -206,6 +226,43 @@ format = "json" # Default: "common"
[accessLog.fields.names]
"ClientUsername" = "drop"
# ...
```
### Customize Headers
Access logs prints the headers of each request, as fields of the access log line.
You can customize which and how the headers are printed, likewise the other fields (see ["Customize Fields" section](#customize-fields)).
Each header has a "mode" which defines how it is written in the access log lines.
The possible values for the mode are:
* `keep`: the header and its value are written on the access log line. This is the default behavior.
* `drop`: the header is not written at all on the access log.
* `redacted`: the header is written, but its value is redacted to avoid leaking sensitive information.
To customize the headers, you must:
* Switch to the JSON format (mandatory)
* Define the "default mode" for all headers (default is `keep`)
* OR Define the headers which does not follow the default mode
!!! important
The headers are written with the prefix `request_` in the access log.
This prefix must not be included when specifying a header in the TOML configuration.
* Do: `"User-Agent" = "drop"`
* Don't: `"redacted_User-Agent" = "drop"`
```toml
[accessLog]
# Access Log Format
#
# Optional
# Default: "common"
#
# Accepted values "common", "json"
#
format = "json"
[accessLog.fields.headers]
# defaultMode
@@ -224,7 +281,7 @@ format = "json" # Default: "common"
# ...
```
### List of all available fields
### List of All Available Fields
| Field | Description |
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -259,6 +316,8 @@ format = "json" # Default: "common"
| `Overhead` | The processing time overhead caused by Traefik. |
| `RetryAttempts` | The amount of attempts the request was retried. |
### Depreciation Notice
Deprecated way (before 1.4):
!!! danger "DEPRECATED"
@@ -272,14 +331,6 @@ Deprecated way (before 1.4):
accessLogsFile = "log/access.log"
```
### CLF - Common Log Format
By default, Traefik use the CLF (`common`) as access log format.
```html
<remote_IP_address> - <client_user_name_if_available> [<timestamp>] "<request_method> <request_path> <request_protocol>" <origin_server_HTTP_status> <origin_server_content_size> "<request_referrer>" "<request_user_agent>" <number_of_requests_received_since_Traefik_started> "<Traefik_frontend_name>" "<Traefik_backend_URL>" <request_duration_in_ms>ms
```
## Log Rotation
Traefik will close and reopen its log files, assuming they're configured, on receipt of a USR1 signal.

View File

@@ -77,7 +77,7 @@ version: '3'
services:
reverse-proxy:
image: traefik # The official Traefik docker image
image: traefik:v1.7 # The official Traefik docker image
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
ports:
- "80:80" # The HTTP port

View File

@@ -91,7 +91,7 @@ To watch docker events, add `--docker.watch`.
version: "3"
services:
traefik:
image: traefik:<stable version from https://hub.docker.com/_/traefik>
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
command:
- "--api"
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
@@ -156,7 +156,7 @@ The initializer in a docker-compose file will be:
```yaml
traefik_init:
image: traefik:<stable version from https://hub.docker.com/_/traefik>
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
command:
- "storeconfig"
- "--api"
@@ -177,7 +177,7 @@ And now, the Traefik part will only have the Consul configuration.
```yaml
traefik:
image: traefik:<stable version from https://hub.docker.com/_/traefik>
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
depends_on:
- traefik_init
- consul
@@ -200,7 +200,7 @@ The new configuration will be stored in Consul, and you need to restart the Trae
version: "3.4"
services:
traefik_init:
image: traefik:<stable version from https://hub.docker.com/_/traefik>
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
command:
- "storeconfig"
- "--api"
@@ -229,7 +229,7 @@ services:
depends_on:
- consul
traefik:
image: traefik:<stable version from https://hub.docker.com/_/traefik>
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
depends_on:
- traefik_init
- consul

View File

@@ -50,7 +50,7 @@ version: '2'
services:
traefik:
image: traefik:<stable version from https://hub.docker.com/_/traefik>
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
restart: always
ports:
- 80:80

View File

@@ -118,7 +118,7 @@ spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik
- image: traefik:v1.7
name: traefik-ingress-lb
ports:
- name: http
@@ -180,7 +180,7 @@ spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik
- image: traefik:v1.7
name: traefik-ingress-lb
ports:
- name: http

View File

@@ -139,7 +139,7 @@ Here is the [docker-compose file](https://docs.docker.com/compose/compose-file/)
```yaml
traefik:
image: traefik:<stable version from https://hub.docker.com/_/traefik>
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
command: --consul --consul.endpoint=127.0.0.1:8500
ports:
- "80:80"

View File

@@ -1,5 +1,5 @@
traefik:
image: traefik
image: traefik:v1.7
command: --api --rancher --rancher.domain=rancher.localhost --rancher.endpoint=http://example.com --rancher.accesskey=XXXXXXX --rancher.secretkey=YYYYYY --logLevel=DEBUG
ports:
- "80:80"

View File

@@ -1,5 +1,5 @@
traefik:
image: traefik
image: traefik:v1.7
command: -c /dev/null --api --docker --docker.domain=docker.localhost --logLevel=DEBUG
ports:
- "80:80"

View File

@@ -26,7 +26,7 @@ spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik
- image: traefik:v1.7
name: traefik-ingress-lb
ports:
- name: http

View File

@@ -22,7 +22,7 @@ spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik
- image: traefik:v1.7
name: traefik-ingress-lb
ports:
- name: http

View File

@@ -13,7 +13,7 @@ version: '3'
services:
reverse-proxy:
image: traefik # The official Traefik docker image
image: traefik:v1.7 # The official Traefik docker image
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
ports:
- "80:80" # The HTTP port
@@ -101,7 +101,7 @@ IP: 172.27.0.4
### 4 — Enjoy Traefik's Magic
Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Traefik work for you!
Whatever your infrastructure is, there is probably [an available Traefik backend](https://docs.traefik.io/#supported-backends) that will do the job.
Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/v1.7/) and let Traefik work for you!
Whatever your infrastructure is, there is probably [an available Traefik backend](https://docs.traefik.io/v1.7/#supported-backends) that will do the job.
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Traefik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Traefik's let's encrypt integration](https://docs.traefik.io/v1.7/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/v1.7/user-guide/docker-and-lets-encrypt/).

View File

@@ -3,7 +3,7 @@ version: '3'
services:
# The reverse proxy service (Traefik)
reverse-proxy:
image: traefik # The official Traefik docker image
image: traefik:v1.7 # The official Traefik docker image
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
ports:
- "80:80" # The HTTP port

View File

@@ -12,7 +12,7 @@ RUN yarn install
RUN npm run build
# BUILD
FROM golang:1.11-alpine as gobuild
FROM golang:1.12-alpine as gobuild
RUN apk --update upgrade \
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \

View File

@@ -3,6 +3,7 @@ package accesslog
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/url"
@@ -32,6 +33,19 @@ const (
JSONFormat = "json"
)
type noopCloser struct {
*os.File
}
func (n noopCloser) Write(p []byte) (int, error) {
return n.File.Write(p)
}
func (n noopCloser) Close() error {
// noop
return nil
}
type logHandlerParams struct {
logDataTable *LogData
crr *captureRequestReader
@@ -42,7 +56,7 @@ type logHandlerParams struct {
type LogHandler struct {
config *types.AccessLog
logger *logrus.Logger
file *os.File
file io.WriteCloser
mu sync.Mutex
httpCodeRanges types.HTTPCodeRanges
logHandlerChan chan logHandlerParams
@@ -51,7 +65,7 @@ type LogHandler struct {
// NewLogHandler creates a new LogHandler
func NewLogHandler(config *types.AccessLog) (*LogHandler, error) {
file := os.Stdout
var file io.WriteCloser = noopCloser{os.Stdout}
if len(config.FilePath) > 0 {
f, err := openAccessLogFile(config.FilePath)
if err != nil {
@@ -205,14 +219,15 @@ func (l *LogHandler) Close() error {
// Rotate closes and reopens the log file to allow for rotation
// by an external source.
func (l *LogHandler) Rotate() error {
var err error
if l.file != nil {
defer func(f *os.File) {
f.Close()
}(l.file)
if l.config.FilePath == "" {
return nil
}
if l.file != nil {
defer func(f io.Closer) { _ = f.Close() }(l.file)
}
var err error
l.file, err = os.OpenFile(l.config.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
if err != nil {
return err

View File

@@ -19,7 +19,10 @@ import (
)
// Compile time validation that the response recorder implements http interfaces correctly.
var _ middlewares.Stateful = &responseRecorderWithCloseNotify{}
var (
_ middlewares.Stateful = &responseRecorderWithCloseNotify{}
_ middlewares.Stateful = &codeCatcherWithCloseNotify{}
)
// Handler is a middleware that provides the custom error pages
type Handler struct {
@@ -74,25 +77,29 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
return
}
recorder := newResponseRecorder(w)
next.ServeHTTP(recorder, req)
catcher := newCodeCatcher(w, h.httpCodeRanges)
next.ServeHTTP(catcher, req)
if !catcher.isFilteredCode() {
return
}
// check the recorder code against the configured http status code ranges
code := catcher.getCode()
for _, block := range h.httpCodeRanges {
if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] {
log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.GetCode())
if code >= block[0] && code <= block[1] {
log.Errorf("Caught HTTP Status Code %d, returning error page", code)
var query string
if len(h.backendQuery) > 0 {
query = "/" + strings.TrimPrefix(h.backendQuery, "/")
query = strings.Replace(query, "{status}", strconv.Itoa(recorder.GetCode()), -1)
query = strings.Replace(query, "{status}", strconv.Itoa(code), -1)
}
pageReq, err := newRequest(h.backendURL + query)
if err != nil {
log.Error(err)
w.WriteHeader(recorder.GetCode())
fmt.Fprint(w, http.StatusText(recorder.GetCode()))
w.WriteHeader(code)
fmt.Fprint(w, http.StatusText(code))
return
}
@@ -102,16 +109,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context()))
utils.CopyHeaders(w.Header(), recorderErrorPage.Header())
w.WriteHeader(recorder.GetCode())
w.WriteHeader(code)
w.Write(recorderErrorPage.GetBody().Bytes())
return
}
}
// did not catch a configured status code so proceed with the request
utils.CopyHeaders(w.Header(), recorder.Header())
w.WriteHeader(recorder.GetCode())
w.Write(recorder.GetBody().Bytes())
}
func newRequest(baseURL string) (*http.Request, error) {
@@ -129,6 +131,133 @@ func newRequest(baseURL string) (*http.Request, error) {
return req, nil
}
type responseInterceptor interface {
http.ResponseWriter
http.Flusher
getCode() int
isFilteredCode() bool
}
// codeCatcher is a response writer that detects as soon as possible whether the
// response is a code within the ranges of codes it watches for. If it is, it
// simply drops the data from the response. Otherwise, it forwards it directly to
// the original client (its responseWriter) without any buffering.
type codeCatcher struct {
headerMap http.Header
code int
httpCodeRanges types.HTTPCodeRanges
firstWrite bool
caughtFilteredCode bool
responseWriter http.ResponseWriter
headersSent bool
err error
}
type codeCatcherWithCloseNotify struct {
*codeCatcher
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone away.
func (cc *codeCatcherWithCloseNotify) CloseNotify() <-chan bool {
return cc.responseWriter.(http.CloseNotifier).CloseNotify()
}
func newCodeCatcher(rw http.ResponseWriter, httpCodeRanges types.HTTPCodeRanges) responseInterceptor {
catcher := &codeCatcher{
headerMap: make(http.Header),
code: http.StatusOK, // If backend does not call WriteHeader on us, we consider it's a 200.
responseWriter: rw,
httpCodeRanges: httpCodeRanges,
firstWrite: true,
}
if _, ok := rw.(http.CloseNotifier); ok {
return &codeCatcherWithCloseNotify{catcher}
}
return catcher
}
func (cc *codeCatcher) Header() http.Header {
if cc.headerMap == nil {
cc.headerMap = make(http.Header)
}
return cc.headerMap
}
func (cc *codeCatcher) getCode() int {
return cc.code
}
// isFilteredCode returns whether the codeCatcher received a response code among the ones it is watching,
// and for which the response should be deferred to the error handler.
func (cc *codeCatcher) isFilteredCode() bool {
return cc.caughtFilteredCode
}
func (cc *codeCatcher) Write(buf []byte) (int, error) {
if !cc.firstWrite {
if cc.caughtFilteredCode {
// We don't care about the contents of the response,
// since we want to serve the ones from the error page,
// so we just drop them.
return len(buf), nil
}
return cc.responseWriter.Write(buf)
}
cc.firstWrite = false
// If WriteHeader was already called from the caller, this is a NOOP.
// Otherwise, cc.code is actually a 200 here.
cc.WriteHeader(cc.code)
if cc.caughtFilteredCode {
return len(buf), nil
}
return cc.responseWriter.Write(buf)
}
func (cc *codeCatcher) WriteHeader(code int) {
if cc.headersSent || cc.caughtFilteredCode {
return
}
cc.code = code
for _, block := range cc.httpCodeRanges {
if cc.code >= block[0] && cc.code <= block[1] {
cc.caughtFilteredCode = true
break
}
}
// it will be up to the other response recorder to send the headers,
// so it is out of our hands now.
if cc.caughtFilteredCode {
return
}
utils.CopyHeaders(cc.responseWriter.Header(), cc.Header())
cc.responseWriter.WriteHeader(cc.code)
cc.headersSent = true
}
// Hijack hijacks the connection
func (cc *codeCatcher) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hj, ok := cc.responseWriter.(http.Hijacker); ok {
return hj.Hijack()
}
return nil, nil, fmt.Errorf("%T is not a http.Hijacker", cc.responseWriter)
}
// Flush sends any buffered data to the client.
func (cc *codeCatcher) Flush() {
// If WriteHeader was already called from the caller, this is a NOOP.
// Otherwise, cc.code is actually a 200 here.
cc.WriteHeader(cc.code)
if flusher, ok := cc.responseWriter.(http.Flusher); ok {
flusher.Flush()
}
}
type responseRecorder interface {
http.ResponseWriter
http.Flusher

View File

@@ -34,6 +34,30 @@ func TestHandler(t *testing.T) {
assert.Contains(t, recorder.Body.String(), http.StatusText(http.StatusOK))
},
},
{
desc: "no error, but not a 200",
errorPage: &types.ErrorPage{Backend: "error", Query: "/test", Status: []string{"500-501", "503-599"}},
backendCode: http.StatusPartialContent,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "My error page.")
}),
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
assert.Equal(t, http.StatusPartialContent, recorder.Code, "HTTP status")
assert.Contains(t, recorder.Body.String(), http.StatusText(http.StatusPartialContent))
},
},
{
desc: "a 304, so no Write called",
errorPage: &types.ErrorPage{Backend: "error", Query: "/test", Status: []string{"500-501", "503-599"}},
backendCode: http.StatusNotModified,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "whatever, should not be called")
}),
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
assert.Equal(t, http.StatusNotModified, recorder.Code, "HTTP status")
assert.Contains(t, recorder.Body.String(), "")
},
},
{
desc: "in the range",
errorPage: &types.ErrorPage{Backend: "error", Query: "/test", Status: []string{"500-501", "503-599"}},
@@ -108,6 +132,9 @@ func TestHandler(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(test.backendCode)
if test.backendCode == http.StatusNotModified {
return
}
fmt.Fprintln(w, http.StatusText(test.backendCode))
})

View File

@@ -5,6 +5,7 @@ import (
"net/http"
"net/http/httptest"
"net/http/httptrace"
"strconv"
"strings"
"testing"
@@ -35,7 +36,7 @@ func TestRetry(t *testing.T) {
desc: "no retry when max request attempts is one",
maxRequestAttempts: 1,
wantRetryAttempts: 0,
wantResponseStatus: http.StatusInternalServerError,
wantResponseStatus: http.StatusBadGateway,
amountFaultyEndpoints: 1,
},
{
@@ -56,7 +57,7 @@ func TestRetry(t *testing.T) {
desc: "max attempts exhausted delivers the 5xx response",
maxRequestAttempts: 3,
wantRetryAttempts: 2,
wantResponseStatus: http.StatusInternalServerError,
wantResponseStatus: http.StatusBadGateway,
amountFaultyEndpoints: 3,
},
}
@@ -82,17 +83,18 @@ func TestRetry(t *testing.T) {
t.Fatalf("Error creating load balancer: %s", err)
}
basePort := 33444
// out of range port
basePort := 1133444
for i := 0; i < test.amountFaultyEndpoints; i++ {
// 192.0.2.0 is a non-routable IP for testing purposes.
// See: https://stackoverflow.com/questions/528538/non-routable-ip-address/18436928#18436928
// We only use the port specification here because the URL is used as identifier
// in the load balancer and using the exact same URL would not add a new server.
loadBalancer.UpsertServer(testhelpers.MustParseURL("http://192.0.2.0:" + string(basePort+i)))
_ = loadBalancer.UpsertServer(testhelpers.MustParseURL("http://192.0.2.0:" + strconv.Itoa(basePort+i)))
}
// add the functioning server to the end of the load balancer list
loadBalancer.UpsertServer(testhelpers.MustParseURL(backendServer.URL))
_ = loadBalancer.UpsertServer(testhelpers.MustParseURL(backendServer.URL))
retryListener := &countingRetryListener{}
retry := NewRetry(test.maxRequestAttempts, loadBalancer, retryListener)
@@ -154,17 +156,18 @@ func TestRetryWebsocket(t *testing.T) {
t.Fatalf("Error creating load balancer: %s", err)
}
basePort := 33444
// out of range port
basePort := 1133444
for i := 0; i < test.amountFaultyEndpoints; i++ {
// 192.0.2.0 is a non-routable IP for testing purposes.
// See: https://stackoverflow.com/questions/528538/non-routable-ip-address/18436928#18436928
// We only use the port specification here because the URL is used as identifier
// in the load balancer and using the exact same URL would not add a new server.
loadBalancer.UpsertServer(testhelpers.MustParseURL("http://192.0.2.0:" + string(basePort+i)))
_ = loadBalancer.UpsertServer(testhelpers.MustParseURL("http://192.0.2.0:" + strconv.Itoa(basePort+i)))
}
// add the functioning server to the end of the load balancer list
loadBalancer.UpsertServer(testhelpers.MustParseURL(backendServer.URL))
_ = loadBalancer.UpsertServer(testhelpers.MustParseURL(backendServer.URL))
retryListener := &countingRetryListener{}
retry := NewRetry(test.maxRequestAttempts, loadBalancer, retryListener)

View File

@@ -16,6 +16,7 @@ import (
"time"
"github.com/cenk/backoff"
"github.com/containous/flaeg"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
@@ -68,6 +69,7 @@ type Provider struct {
LabelSelector string `description:"Kubernetes Ingress label selector to use" export:"true"`
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true"`
IngressEndpoint *IngressEndpoint `description:"Kubernetes Ingress Endpoint"`
ThrottleDuration flaeg.Duration `description:"Ingress refresh throttle duration"`
lastConfiguration safe.Safe
}
@@ -137,16 +139,29 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
return nil
}
}
throttleDuration := time.Duration(p.ThrottleDuration)
throttledChan := throttleEvents(throttleDuration, stop, eventsChan)
if throttledChan != nil {
eventsChan = throttledChan
}
for {
select {
case <-stop:
return nil
case event := <-eventsChan:
// Note that event is the *first* event that came in during this
// throttling interval -- if we're hitting our throttle, we may have
// dropped events. This is fine, because we don't treat different
// event types differently. But if we do in the future, we'll need to
// track more information about the dropped events.
log.Debugf("Received Kubernetes event kind %T", event)
templateObjects, err := p.loadIngresses(k8sClient)
if err != nil {
return err
}
if reflect.DeepEqual(p.lastConfiguration.Get(), templateObjects) {
log.Debugf("Skipping Kubernetes event kind %T", event)
} else {
@@ -156,6 +171,11 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
Configuration: p.loadConfig(*templateObjects),
}
}
// If we're throttling, we sleep here for the throttle duration to
// enforce that we don't refresh faster than our throttle. time.Sleep
// returns immediately if p.ThrottleDuration is 0 (no throttle).
time.Sleep(throttleDuration)
}
}
}
@@ -599,6 +619,39 @@ func (p *Provider) addGlobalBackend(cl Client, i *extensionsv1beta1.Ingress, tem
return nil
}
func throttleEvents(throttleDuration time.Duration, stop chan bool, eventsChan <-chan interface{}) chan interface{} {
if throttleDuration == 0 {
return nil
}
// Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling)
eventsChanBuffered := make(chan interface{}, 1)
// Run a goroutine that reads events from eventChan and does a
// non-blocking write to pendingEvent. This guarantees that writing to
// eventChan will never block, and that pendingEvent will have
// something in it if there's been an event since we read from that channel.
go func() {
for {
select {
case <-stop:
return
case nextEvent := <-eventsChan:
select {
case eventsChanBuffered <- nextEvent:
default:
// We already have an event in eventsChanBuffered, so we'll
// do a refresh as soon as our throttle allows us to. It's fine
// to drop the event and keep whatever's in the buffer -- we
// don't do different things for different events
log.Debugf("Dropping event kind %T due to throttling", nextEvent)
}
}
}
}()
return eventsChanBuffered
}
func getRuleForPath(pa extensionsv1beta1.HTTPIngressPath, i *extensionsv1beta1.Ingress) (string, error) {
if len(pa.Path) == 0 {
return "", nil
@@ -940,12 +993,12 @@ func getForwardAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*type
}
authSecretName := getStringValue(i.Annotations, annotationKubernetesAuthForwardTLSSecret, "")
if len(authSecretName) > 0 {
authSecretCert, authSecretKey, err := loadAuthTLSSecret(i.Namespace, authSecretName, k8sClient)
if err != nil {
return nil, fmt.Errorf("failed to load auth secret: %s", err)
}
authSecretCert, authSecretKey, err := loadAuthTLSSecret(i.Namespace, authSecretName, k8sClient)
if err != nil {
return nil, fmt.Errorf("failed to load auth secret: %s", err)
}
if authSecretCert != "" || authSecretKey != "" {
forwardAuth.TLS = &types.ClientTLS{
Cert: authSecretCert,
Key: authSecretKey,
@@ -953,10 +1006,20 @@ func getForwardAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*type
}
}
if forwardAuth.TLS == nil && label.Has(i.Annotations, getAnnotationName(i.Annotations, annotationKubernetesAuthForwardTLSInsecure)) {
forwardAuth.TLS = &types.ClientTLS{
InsecureSkipVerify: getBoolValue(i.Annotations, annotationKubernetesAuthForwardTLSInsecure, false),
}
}
return forwardAuth, nil
}
func loadAuthTLSSecret(namespace, secretName string, k8sClient Client) (string, string, error) {
if len(secretName) == 0 {
return "", "", nil
}
secret, exists, err := k8sClient.GetSecret(namespace, secretName)
if err != nil {
return "", "", fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)

View File

@@ -5,7 +5,7 @@ if [ -z "${VALIDATE_UPSTREAM:-}" ]; then
# are running more than one validate bundlescript
VALIDATE_REPO='https://github.com/containous/traefik.git'
VALIDATE_BRANCH='master'
VALIDATE_BRANCH='v1.7'
# Should not be needed for now O:)
# if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then

View File

@@ -3,9 +3,8 @@ set -e
# List of bundles to create when no argument is passed
DEFAULT_BUNDLES=(
validate-gofmt
validate-govet
generate
validate-gofmt
binary
test-unit

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env bash
source "$(dirname "$BASH_SOURCE")/.validate"
IFS=$'\n'
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
unset IFS
errors=()
failedErrcheck=$(errcheck .)
if [ "$failedErrcheck" ]; then
errors+=( "$failedErrcheck" )
fi
if [ ${#errors[@]} -eq 0 ]; then
echo 'Congratulations! All Go source files have been errchecked.'
else
{
echo "Errors from errcheck:"
for err in "${errors[@]}"; do
echo "$err"
done
echo
echo 'Please fix the above errors. You can test via "errcheck" and commit the result.'
echo
} >&2
false
fi

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env bash
source "$(dirname "$BASH_SOURCE")/.validate"
IFS=$'\n'
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
unset IFS
errors=()
for f in "${files[@]}"; do
# we use "git show" here to validate that what's committed passes go vet
failedVet=$(go tool vet -printf=false "$f")
if [ "$failedVet" ]; then
errors+=( "$failedVet" )
fi
done
if [ ${#errors[@]} -eq 0 ]; then
echo 'Congratulations! All Go source files have been vetted.'
else
{
echo "Errors from govet:"
for err in "${errors[@]}"; do
echo "$err"
done
echo
echo 'Please fix the above errors. You can test via "go vet" and commit the result.'
echo
} >&2
false
fi

View File

@@ -85,6 +85,8 @@ func (h *hijackConnectionTracker) Shutdown(ctx context.Context) error {
// Close close all the connections in the tracked connections list
func (h *hijackConnectionTracker) Close() {
h.lock.Lock()
defer h.lock.Unlock()
for conn := range h.conns {
if err := conn.Close(); err != nil {
log.Errorf("Error while closing Hijacked conn: %v", err)

View File

@@ -59,7 +59,9 @@
{{if $frontend.Auth }}
[frontends."{{ $frontendName }}".auth]
headerField = "X-WebAuth-User"
{{if $frontend.Auth.HeaderField }}
headerField = "{{ $frontend.Auth.HeaderField }}"
{{end}}
{{if $frontend.Auth.Basic }}
[frontends."{{ $frontendName }}".auth.basic]

View File

@@ -16,36 +16,41 @@ import (
var (
// MinVersion Map of allowed TLS minimum versions
MinVersion = map[string]uint16{
`VersionTLS10`: tls.VersionTLS10,
`VersionTLS11`: tls.VersionTLS11,
`VersionTLS12`: tls.VersionTLS12,
"VersionTLS10": tls.VersionTLS10,
"VersionTLS11": tls.VersionTLS11,
"VersionTLS12": tls.VersionTLS12,
"VersionTLS13": tls.VersionTLS13,
}
// CipherSuites Map of TLS CipherSuites from crypto/tls
// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants
CipherSuites = map[string]uint16{
`TLS_RSA_WITH_RC4_128_SHA`: tls.TLS_RSA_WITH_RC4_128_SHA,
`TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
`TLS_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_RSA_WITH_AES_128_CBC_SHA,
`TLS_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_RSA_WITH_AES_256_CBC_SHA,
`TLS_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
`TLS_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
`TLS_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
`TLS_ECDHE_ECDSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
`TLS_ECDHE_RSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
`TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
`TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
`TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
`TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
"TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
"TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384,
"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
"TLS_FALLBACK_SCSV": tls.TLS_FALLBACK_SCSV,
}
)

View File

@@ -2,6 +2,7 @@ package tls
import (
"crypto/tls"
"crypto/x509"
"net"
"sort"
"strings"
@@ -47,6 +48,11 @@ func (c CertificateStore) GetAllDomains() []string {
allCerts = append(allCerts, domains)
}
}
// Get Default certificate
if c.DefaultCertificate != nil {
allCerts = append(allCerts, getCertificateDomains(c.DefaultCertificate)...)
}
return allCerts
}
@@ -115,6 +121,27 @@ func (c CertificateStore) ResetCache() {
}
}
func getCertificateDomains(cert *tls.Certificate) []string {
if cert == nil {
return nil
}
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return nil
}
var names []string
if len(x509Cert.Subject.CommonName) > 0 {
names = append(names, x509Cert.Subject.CommonName)
}
for _, san := range x509Cert.DNSNames {
names = append(names, san)
}
return names
}
// MatchDomain return true if a domain match the cert domain
func MatchDomain(domain string, certDomain string) bool {
if domain == certDomain {

View File

@@ -13,6 +13,90 @@ import (
"github.com/stretchr/testify/require"
)
func TestGetAllDomains(t *testing.T) {
testCases := []struct {
desc string
staticCert string
dynamicCert string
defaultCert string
expectedDomains []string
}{
{
desc: "Empty Store, returns no domains",
staticCert: "",
dynamicCert: "",
defaultCert: "",
expectedDomains: nil,
},
{
desc: "Static cert domains",
staticCert: "snitest.com",
dynamicCert: "",
defaultCert: "",
expectedDomains: []string{"snitest.com"},
},
{
desc: "Dynamic cert domains",
staticCert: "",
dynamicCert: "snitest.com",
defaultCert: "",
expectedDomains: []string{"snitest.com"},
},
{
desc: "Default cert domains",
staticCert: "",
dynamicCert: "",
defaultCert: "snitest.com",
expectedDomains: []string{"snitest.com"},
},
{
desc: "All domains",
staticCert: "www.snitest.com",
dynamicCert: "*.snitest.com",
defaultCert: "snitest.com",
expectedDomains: []string{"www.snitest.com", "*.snitest.com", "snitest.com"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
staticMap := map[string]*tls.Certificate{}
if test.staticCert != "" {
cert, err := loadTestCert(test.staticCert, false)
require.NoError(t, err)
staticMap[strings.ToLower(test.staticCert)] = cert
}
dynamicMap := map[string]*tls.Certificate{}
if test.dynamicCert != "" {
cert, err := loadTestCert(test.dynamicCert, false)
require.NoError(t, err)
dynamicMap[strings.ToLower(test.dynamicCert)] = cert
}
var defaultCert *tls.Certificate
if test.defaultCert != "" {
cert, err := loadTestCert(test.defaultCert, false)
require.NoError(t, err)
defaultCert = cert
}
store := &CertificateStore{
DynamicCerts: safe.New(dynamicMap),
StaticCerts: safe.New(staticMap),
DefaultCertificate: defaultCert,
CertCache: cache.New(1*time.Hour, 10*time.Minute),
}
actual := store.GetAllDomains()
assert.Equal(t, test.expectedDomains, actual)
})
}
}
func TestGetBestCertificate(t *testing.T) {
testCases := []struct {
desc string
@@ -116,15 +200,15 @@ func TestGetBestCertificate(t *testing.T) {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
staticMap := map[string]*tls.Certificate{}
dynamicMap := map[string]*tls.Certificate{}
staticMap := map[string]*tls.Certificate{}
if test.staticCert != "" {
cert, err := loadTestCert(test.staticCert, test.uppercase)
require.NoError(t, err)
staticMap[strings.ToLower(test.staticCert)] = cert
}
dynamicMap := map[string]*tls.Certificate{}
if test.dynamicCert != "" {
cert, err := loadTestCert(test.dynamicCert, test.uppercase)
require.NoError(t, err)

1
webui/.gitignore vendored
View File

@@ -8,6 +8,7 @@
# dependencies
/node_modules
/.quasar
# IDEs and editors
/.idea

View File

@@ -25,7 +25,7 @@
<a class="navbar-item" [href]="releaseLink" target="_blank">
{{ version }} / {{ codename }}
</a>
<a class="navbar-item" href="https://docs.traefik.io" target="_blank">
<a class="navbar-item" href="https://docs.traefik.io/v1.7" target="_blank">
Documentation
</a>
</div>