forked from SW/traefik
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74ad83f05a | ||
|
|
d707c8ba93 | ||
|
|
640eb62ca1 | ||
|
|
216710864e | ||
|
|
226f20b626 | ||
|
|
151be83bce | ||
|
|
d1a8c7fa78 | ||
|
|
254dc38c3d | ||
|
|
24d084d7e6 | ||
|
|
df5f530058 | ||
|
|
753d173965 | ||
|
|
ffd1f122de | ||
|
|
f98b57fdf4 | ||
|
|
2d37f08864 | ||
|
|
a7dbcc282c | ||
|
|
f4f62e7fb3 | ||
|
|
4cae8bcb10 | ||
|
|
bee370ec6b | ||
|
|
f1d016b893 | ||
|
|
4defbbe848 | ||
|
|
f397342f16 | ||
|
|
989a59cc29 | ||
|
|
c5b71592c8 |
20
.semaphoreci/golang.sh
Executable file
20
.semaphoreci/golang.sh
Executable 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
|
||||
@@ -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)"
|
||||
|
||||
48
CHANGELOG.md
48
CHANGELOG.md
@@ -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'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 & 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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -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 .
|
||||
|
||||
30
README.md
30
README.md
@@ -4,7 +4,7 @@
|
||||
</p>
|
||||
|
||||
[](https://semaphoreci.com/containous/traefik)
|
||||
[](https://docs.traefik.io)
|
||||
[](https://docs.traefik.io/v1.7)
|
||||
[](http://goreportcard.com/report/containous/traefik)
|
||||
[](https://microbadger.com/images/traefik)
|
||||
[](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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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. |
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
1
webui/.gitignore
vendored
@@ -8,6 +8,7 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.quasar
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user