Merge branch 'v1.5' into master
This commit is contained in:
commit
c84fb9895e
35 changed files with 462 additions and 124 deletions
|
@ -1,5 +1,13 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## [v1.4.6](https://github.com/containous/traefik/tree/v1.4.6) (2018-01-02)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v1.4.5...v1.4.6)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[docker]** Normalize serviceName added to the service backend names ([#2631](https://github.com/containous/traefik/pull/2631) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[websocket]** Use gorilla readMessage and writeMessage instead of just an io.Copy ([#2640](https://github.com/containous/traefik/pull/2640) by [Juliens](https://github.com/Juliens))
|
||||||
|
- Fix bug report command ([#2638](https://github.com/containous/traefik/pull/2638) by [ldez](https://github.com/ldez))
|
||||||
|
|
||||||
## [v1.5.0-rc3](https://github.com/containous/traefik/tree/v1.5.0-rc3) (2017-12-20)
|
## [v1.5.0-rc3](https://github.com/containous/traefik/tree/v1.5.0-rc3) (2017-12-20)
|
||||||
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc2...v1.5.0-rc3)
|
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc2...v1.5.0-rc3)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016-2017 Containous SAS
|
Copyright (c) 2016-2018 Containous SAS
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -304,6 +304,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{ if hasHeaders $container}}
|
||||||
[frontends."frontend-{{$frontend}}".headers]
|
[frontends."frontend-{{$frontend}}".headers]
|
||||||
{{if hasSSLRedirectHeaders $container}}
|
{{if hasSSLRedirectHeaders $container}}
|
||||||
SSLRedirect = {{getSSLRedirectHeaders $container}}
|
SSLRedirect = {{getSSLRedirectHeaders $container}}
|
||||||
|
@ -383,6 +384,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
|
||||||
{{$k}} = "{{$v}}"
|
{{$k}} = "{{$v}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
||||||
rule = "{{getFrontendRule $container}}"
|
rule = "{{getFrontendRule $container}}"
|
||||||
|
@ -536,6 +538,7 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
|
||||||
replacement = "{{$frontend.RedirectReplacement}}"
|
replacement = "{{$frontend.RedirectReplacement}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if $frontend.Headers }}
|
||||||
[frontends."{{$frontendName}}".headers]
|
[frontends."{{$frontendName}}".headers]
|
||||||
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
|
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
|
||||||
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
|
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
|
||||||
|
@ -579,13 +582,13 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
|
||||||
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
|
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
|
||||||
{{$k}} = "{{$v}}"
|
{{$k}} = "{{$v}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{range $routeName, $route := $frontend.Routes}}
|
{{range $routeName, $route := $frontend.Routes}}
|
||||||
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
||||||
rule = "{{$route.Rule}}"
|
rule = "{{$route.Rule}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}`)
|
||||||
`)
|
|
||||||
|
|
||||||
func templatesKubernetesTmplBytes() ([]byte, error) {
|
func templatesKubernetesTmplBytes() ([]byte, error) {
|
||||||
return _templatesKubernetesTmpl, nil
|
return _templatesKubernetesTmpl, nil
|
||||||
|
@ -805,6 +808,7 @@ var _templatesMarathonTmpl = []byte(`{{$apps := .Applications}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if hasHeaders $app $serviceName }}
|
||||||
[frontends."{{ getFrontendName $app $serviceName }}".headers]
|
[frontends."{{ getFrontendName $app $serviceName }}".headers]
|
||||||
{{if hasSSLRedirectHeaders $app $serviceName}}
|
{{if hasSSLRedirectHeaders $app $serviceName}}
|
||||||
SSLRedirect = {{getSSLRedirectHeaders $app $serviceName}}
|
SSLRedirect = {{getSSLRedirectHeaders $app $serviceName}}
|
||||||
|
@ -884,6 +888,7 @@ var _templatesMarathonTmpl = []byte(`{{$apps := .Applications}}
|
||||||
{{$k}} = "{{$v}}"
|
{{$k}} = "{{$v}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
[frontends."{{ getFrontendName $app $serviceName }}".routes."route-host{{$app.ID | replace "/" "-"}}{{getServiceNameSuffix $serviceName }}"]
|
[frontends."{{ getFrontendName $app $serviceName }}".routes."route-host{{$app.ID | replace "/" "-"}}{{getServiceNameSuffix $serviceName }}"]
|
||||||
rule = "{{getFrontendRule $app $serviceName}}"
|
rule = "{{getFrontendRule $app $serviceName}}"
|
||||||
|
|
|
@ -84,7 +84,7 @@ Add more configuration information here.
|
||||||
)
|
)
|
||||||
|
|
||||||
// newBugCmd builds a new Bug command
|
// newBugCmd builds a new Bug command
|
||||||
func newBugCmd(traefikConfiguration interface{}, traefikPointersConfiguration interface{}) *flaeg.Command {
|
func newBugCmd(traefikConfiguration *TraefikConfiguration, traefikPointersConfiguration *TraefikConfiguration) *flaeg.Command {
|
||||||
|
|
||||||
//version Command init
|
//version Command init
|
||||||
return &flaeg.Command{
|
return &flaeg.Command{
|
||||||
|
@ -99,7 +99,7 @@ func newBugCmd(traefikConfiguration interface{}, traefikPointersConfiguration in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBugCmd(traefikConfiguration interface{}) func() error {
|
func runBugCmd(traefikConfiguration *TraefikConfiguration) func() error {
|
||||||
return func() error {
|
return func() error {
|
||||||
|
|
||||||
body, err := createBugReport(traefikConfiguration)
|
body, err := createBugReport(traefikConfiguration)
|
||||||
|
@ -113,7 +113,7 @@ func runBugCmd(traefikConfiguration interface{}) func() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBugReport(traefikConfiguration interface{}) (string, error) {
|
func createBugReport(traefikConfiguration *TraefikConfiguration) (string, error) {
|
||||||
var version bytes.Buffer
|
var version bytes.Buffer
|
||||||
if err := getVersionPrint(&version); err != nil {
|
if err := getVersionPrint(&version); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -124,7 +124,7 @@ func createBugReport(traefikConfiguration interface{}) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := anonymize.Do(&traefikConfiguration, true)
|
config, err := anonymize.Do(traefikConfiguration, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,27 @@ import (
|
||||||
"github.com/containous/traefik/configuration"
|
"github.com/containous/traefik/configuration"
|
||||||
"github.com/containous/traefik/provider/file"
|
"github.com/containous/traefik/provider/file"
|
||||||
"github.com/containous/traefik/tls"
|
"github.com/containous/traefik/tls"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_createBugReport(t *testing.T) {
|
func Test_createBugReport(t *testing.T) {
|
||||||
traefikConfiguration := TraefikConfiguration{
|
traefikConfiguration := &TraefikConfiguration{
|
||||||
ConfigFile: "FOO",
|
ConfigFile: "FOO",
|
||||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||||
EntryPoints: configuration.EntryPoints{
|
EntryPoints: configuration.EntryPoints{
|
||||||
"goo": &configuration.EntryPoint{
|
"goo": &configuration.EntryPoint{
|
||||||
Address: "hoo.bar",
|
Address: "hoo.bar",
|
||||||
|
Auth: &types.Auth{
|
||||||
|
Basic: &types.Basic{
|
||||||
|
UsersFile: "foo Basic UsersFile",
|
||||||
|
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
|
||||||
|
},
|
||||||
|
Digest: &types.Digest{
|
||||||
|
UsersFile: "foo Digest UsersFile",
|
||||||
|
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
File: &file.Provider{
|
File: &file.Provider{
|
||||||
|
@ -28,6 +39,11 @@ func Test_createBugReport(t *testing.T) {
|
||||||
|
|
||||||
report, err := createBugReport(traefikConfiguration)
|
report, err := createBugReport(traefikConfiguration)
|
||||||
assert.NoError(t, err, report)
|
assert.NoError(t, err, report)
|
||||||
|
|
||||||
|
// exported anonymous configuration
|
||||||
|
assert.NotContains(t, "web Basic Users ", report)
|
||||||
|
assert.NotContains(t, "foo Digest Users ", report)
|
||||||
|
assert.NotContains(t, "hoo.bar", report)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_anonymize_traefikConfiguration(t *testing.T) {
|
func Test_anonymize_traefikConfiguration(t *testing.T) {
|
||||||
|
|
|
@ -150,12 +150,15 @@ domain = "marathon.localhost"
|
||||||
|
|
||||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
|
|
||||||
|
|
||||||
## Labels: overriding default behaviour
|
## Labels: overriding default behaviour
|
||||||
|
|
||||||
### On Containers
|
Marathon labels may be used to dynamically change the routing and forwarding behaviour.
|
||||||
|
|
||||||
Labels can be used on containers to override default behaviour:
|
They may be specified on one of two levels: Application or service.
|
||||||
|
|
||||||
|
### Application Level
|
||||||
|
|
||||||
|
The following labels can be defined on Marathon applications. They adjust the behaviour for the entire application.
|
||||||
|
|
||||||
| Label | Description |
|
| Label | Description |
|
||||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
@ -219,9 +222,9 @@ Labels can be used on containers to override default behaviour:
|
||||||
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
|
||||||
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
|
||||||
|
|
||||||
### On Services
|
### Service Level
|
||||||
|
|
||||||
If several ports need to be exposed from a container, the services labels can be used:
|
For applications that expose multiple ports, specific labels can be used to extract one frontend/backend configuration pair per port. Each such pair is called a _service_. The (freely choosable) name of the service is an integral part of the service label name.
|
||||||
|
|
||||||
| Label | Description |
|
| Label | Description |
|
||||||
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||||
|
|
4
docs/theme/partials/footer.html
vendored
4
docs/theme/partials/footer.html
vendored
|
@ -20,7 +20,7 @@
|
||||||
IN THE SOFTWARE.
|
IN THE SOFTWARE.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
{% import "partials/language.html" as lang %}
|
{% import "partials/language.html" as lang with context %}
|
||||||
|
|
||||||
<!-- Application footer -->
|
<!-- Application footer -->
|
||||||
<footer class="md-footer">
|
<footer class="md-footer">
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
|
|
||||||
<!-- Social links -->
|
<!-- Social links -->
|
||||||
{% block social %}
|
{% block social %}
|
||||||
{% include "partials/social.html" %}
|
{% include "partials/social.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,6 +28,24 @@ Following is the order by which Traefik tries to identify the port (the first on
|
||||||
1. The port from the application's `portDefinitions` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one).
|
1. The port from the application's `portDefinitions` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one).
|
||||||
1. The port from the application's `ipAddressPerTask` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one).
|
1. The port from the application's `ipAddressPerTask` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one).
|
||||||
|
|
||||||
|
## Applications with multiple ports
|
||||||
|
|
||||||
|
Some Marathon applications may expose multiple ports. Traefik supports creating one so-called _service_ per port using [specific labels](/configuration/backends/marathon#service-level).
|
||||||
|
|
||||||
|
For instance, assume that a Marathon application exposes a web API on port 80 and an admin interface on port 8080. It would then be possible to make each service available by specifying the following Marathon labels:
|
||||||
|
|
||||||
|
```
|
||||||
|
traefik.web.port=80
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
traefik.admin.port=8080
|
||||||
|
```
|
||||||
|
|
||||||
|
(Note that the service names `web` and `admin` can be chosen arbitrarily.)
|
||||||
|
|
||||||
|
Technically, Traefik will create one pair of frontend and backend configurations for each service.
|
||||||
|
|
||||||
## Achieving high availability
|
## Achieving high availability
|
||||||
|
|
||||||
### Scenarios
|
### Scenarios
|
||||||
|
|
|
@ -86,7 +86,7 @@ docker $(docker-machine config mhs-demo0) run \
|
||||||
-c /dev/null \
|
-c /dev/null \
|
||||||
--docker \
|
--docker \
|
||||||
--docker.domain=traefik \
|
--docker.domain=traefik \
|
||||||
--docker.endpoint=tcp://$(docker-machine ip mhs-demo0):3376 \
|
--docker.endpoint=tcp://$(docker-machine ip mhs-demo0):2376 \
|
||||||
--docker.tls \
|
--docker.tls \
|
||||||
--docker.tls.ca=/ssl/ca.pem \
|
--docker.tls.ca=/ssl/ca.pem \
|
||||||
--docker.tls.cert=/ssl/server.pem \
|
--docker.tls.cert=/ssl/server.pem \
|
||||||
|
@ -105,7 +105,7 @@ Let's explain this command:
|
||||||
| `-v /var/lib/boot2docker/:/ssl` | mount the ssl keys generated by docker-machine |
|
| `-v /var/lib/boot2docker/:/ssl` | mount the ssl keys generated by docker-machine |
|
||||||
| `-c /dev/null` | empty config file |
|
| `-c /dev/null` | empty config file |
|
||||||
| `--docker` | enable docker backend |
|
| `--docker` | enable docker backend |
|
||||||
| `--docker.endpoint=tcp://172.18.0.1:3376` | connect to the swarm master using the docker_gwbridge network |
|
| `--docker.endpoint=tcp://172.18.0.1:2376` | connect to the swarm master using the docker_gwbridge network |
|
||||||
| `--docker.tls` | enable TLS using the docker-machine keys |
|
| `--docker.tls` | enable TLS using the docker-machine keys |
|
||||||
| `--web` | activate the webUI on port 8080 |
|
| `--web` | activate the webUI on port 8080 |
|
||||||
|
|
||||||
|
|
|
@ -577,3 +577,109 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) {
|
||||||
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||||
c.Assert(cn, checker.Equals, "snitest.org")
|
c.Assert(cn, checker.Equals, "snitest.org")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) {
|
||||||
|
// start Træfik
|
||||||
|
cmd, display := s.traefikCmd(
|
||||||
|
withConfigFile("fixtures/etcd/simple_https.toml"),
|
||||||
|
"--etcd",
|
||||||
|
"--etcd.endpoint="+ipEtcd+":4001",
|
||||||
|
"--etcd.useAPIV3=true")
|
||||||
|
defer display(c)
|
||||||
|
|
||||||
|
// prepare to config
|
||||||
|
snitestComCert, err := ioutil.ReadFile("fixtures/https/snitest.com.cert")
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
snitestComKey, err := ioutil.ReadFile("fixtures/https/snitest.com.key")
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
backend1 := map[string]string{
|
||||||
|
"/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
|
||||||
|
"/traefik/backends/backend1/servers/server1/url": "http://" + ipWhoami01 + ":80",
|
||||||
|
"/traefik/backends/backend1/servers/server1/weight": "1",
|
||||||
|
"/traefik/backends/backend1/servers/server2/url": "http://" + ipWhoami02 + ":80",
|
||||||
|
"/traefik/backends/backend1/servers/server2/weight": "1",
|
||||||
|
}
|
||||||
|
|
||||||
|
frontend1 := map[string]string{
|
||||||
|
"/traefik/frontends/frontend1/backend": "backend1",
|
||||||
|
"/traefik/frontends/frontend1/entrypoints": "https",
|
||||||
|
"/traefik/frontends/frontend1/priority": "1",
|
||||||
|
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:snitest.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsconfigure1 := map[string]string{
|
||||||
|
"/traefik/tlsconfiguration/snitestcom/entrypoints": "https",
|
||||||
|
"/traefik/tlsconfiguration/snitestcom/certificate/keyfile": string(snitestComKey),
|
||||||
|
"/traefik/tlsconfiguration/snitestcom/certificate/certfile": string(snitestComCert),
|
||||||
|
}
|
||||||
|
|
||||||
|
// config backends,frontends and first tls keypair
|
||||||
|
for key, value := range backend1 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range frontend1 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range tlsconfigure1 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr1 := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: "snitest.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for etcd
|
||||||
|
err = try.Do(60*time.Second, func() error {
|
||||||
|
_, err := s.kv.Get("/traefik/tlsconfiguration/snitestcom/certificate/keyfile", nil)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// wait for Træfik
|
||||||
|
err = try.GetRequest(traefikWebEtcdURL+"api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h")))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
client := &http.Client{Transport: tr1}
|
||||||
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
|
req.Header.Set("Accept", "*/*")
|
||||||
|
var resp *http.Response
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||||
|
c.Assert(cn, checker.Equals, "snitest.com")
|
||||||
|
|
||||||
|
// now we delete the tls cert/key pairs,so the endpoint show use default cert/key pair
|
||||||
|
for key := range tlsconfigure1 {
|
||||||
|
err := s.kv.Delete(key)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// waiting for Træfik to pull configuration
|
||||||
|
err = try.GetRequest(traefikWebEtcdURL+"api/providers", 30*time.Second, try.BodyNotContains("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
client = &http.Client{Transport: tr1}
|
||||||
|
req.Host = tr1.TLSClientConfig.ServerName
|
||||||
|
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
|
||||||
|
req.Header.Set("Accept", "*/*")
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||||
|
c.Assert(cn, checker.Equals, "TRAEFIK DEFAULT CERT")
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@ defaultEntryPoints = ["https"]
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":4443"
|
address = ":4443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
|
[entryPoints.https02]
|
||||||
|
address = ":8443"
|
||||||
|
[entryPoints.https02.tls]
|
||||||
|
|
||||||
[web]
|
[web]
|
||||||
address = ":8080"
|
address = ":8080"
|
||||||
|
|
|
@ -353,7 +353,7 @@ func startTestServer(port string, statusCode int) (ts *httptest.Server) {
|
||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestWithSNIConfigRoute involves a client sending HTTPS requests with
|
// TestWithSNIDynamicConfigRouteWithNoChange involves a client sending HTTPS requests with
|
||||||
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
|
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
|
||||||
// that traefik routes the requests to the expected backends thanks to given certificate if possible
|
// that traefik routes the requests to the expected backends thanks to given certificate if possible
|
||||||
// otherwise thanks to the default one.
|
// otherwise thanks to the default one.
|
||||||
|
@ -424,7 +424,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) {
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusNoContent)
|
c.Assert(resp.StatusCode, checker.Equals, http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestWithSNIConfigRoute involves a client sending HTTPS requests with
|
// TestWithSNIDynamicConfigRouteWithChange involves a client sending HTTPS requests with
|
||||||
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
|
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
|
||||||
// that traefik updates its configuration when the HTTPS configuration is modified and
|
// that traefik updates its configuration when the HTTPS configuration is modified and
|
||||||
// it routes the requests to the expected backends thanks to given certificate if possible
|
// it routes the requests to the expected backends thanks to given certificate if possible
|
||||||
|
@ -479,7 +479,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
|
|
||||||
// Change certificates configuration file content
|
// Change certificates configuration file content
|
||||||
modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName)
|
modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName, "https")
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
err = try.Do(30*time.Second, func() error {
|
err = try.Do(30*time.Second, func() error {
|
||||||
resp, err = client.Do(req)
|
resp, err = client.Do(req)
|
||||||
|
@ -525,29 +525,121 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound)
|
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file.
|
// TestWithSNIDynamicConfigRouteWithChangeForEmptyTlsConfiguration involves a client sending HTTPS requests with
|
||||||
func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName string) {
|
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
|
||||||
tlsConf := types.Configuration{
|
// that traefik updates its configuration when the HTTPS configuration is modified, even if it totally deleted, and
|
||||||
TLSConfiguration: []*traefikTls.Configuration{
|
// it routes the requests to the expected backends thanks to given certificate if possible
|
||||||
{
|
// otherwise thanks to the default one.
|
||||||
Certificate: &traefikTls.Certificate{
|
func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c *check.C) {
|
||||||
CertFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".cert"),
|
dynamicConfFileName := s.adaptFile(c, "fixtures/https/dynamic_https.toml", struct{}{})
|
||||||
KeyFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".key"),
|
defer os.Remove(dynamicConfFileName)
|
||||||
},
|
confFileName := s.adaptFile(c, "fixtures/https/dynamic_https_sni.toml", struct {
|
||||||
},
|
DynamicConfFileName string
|
||||||
|
}{
|
||||||
|
DynamicConfFileName: dynamicConfFileName,
|
||||||
|
})
|
||||||
|
defer os.Remove(confFileName)
|
||||||
|
cmd, display := s.traefikCmd(withConfigFile(confFileName))
|
||||||
|
defer display(c)
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
tr2 := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: "snitest.org",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var confBuffer bytes.Buffer
|
|
||||||
e := toml.NewEncoder(&confBuffer)
|
// wait for Traefik
|
||||||
err := e.Encode(tlsConf)
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:"+tr2.TLSClientConfig.ServerName))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
backend2 := startTestServer("9020", http.StatusResetContent)
|
||||||
|
|
||||||
|
defer backend2.Close()
|
||||||
|
|
||||||
|
err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||||
|
client := &http.Client{Transport: tr2}
|
||||||
|
req.Host = tr2.TLSClientConfig.ServerName
|
||||||
|
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
|
||||||
|
req.Header.Set("Accept", "*/*")
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
|
err = try.Do(30*time.Second, func() error {
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
|
||||||
|
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
|
||||||
|
req.Close = true
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||||
|
if cn != tr2.TLSClientConfig.ServerName {
|
||||||
|
return fmt.Errorf("domain %s found in place of %s", cn, tr2.TLSClientConfig.ServerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, http.StatusResetContent)
|
||||||
|
// Change certificates configuration file content
|
||||||
|
modifyCertificateConfFileContent(c, "", dynamicConfFileName, "https02")
|
||||||
|
|
||||||
|
err = try.Do(60*time.Second, func() error {
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
|
||||||
|
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
|
||||||
|
req.Close = true
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
|
||||||
|
if cn == tr2.TLSClientConfig.ServerName {
|
||||||
|
return fmt.Errorf("domain %s found in place of default one", tr2.TLSClientConfig.ServerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file.
|
||||||
|
func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName, entryPoint string) {
|
||||||
f, err := os.OpenFile("./"+confFileName, os.O_WRONLY, os.ModeExclusive)
|
f, err := os.OpenFile("./"+confFileName, os.O_WRONLY, os.ModeExclusive)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer func() {
|
defer func() {
|
||||||
f.Close()
|
f.Close()
|
||||||
}()
|
}()
|
||||||
f.Truncate(0)
|
f.Truncate(0)
|
||||||
_, err = f.Write(confBuffer.Bytes())
|
// If certificate file is not provided, just truncate the configuration file
|
||||||
c.Assert(err, checker.IsNil)
|
if len(certFileName) > 0 {
|
||||||
|
tlsConf := types.Configuration{
|
||||||
|
TLSConfiguration: []*traefikTls.Configuration{
|
||||||
|
{
|
||||||
|
Certificate: &traefikTls.Certificate{
|
||||||
|
CertFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".cert"),
|
||||||
|
KeyFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".key"),
|
||||||
|
},
|
||||||
|
EntryPoints: []string{entryPoint},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var confBuffer bytes.Buffer
|
||||||
|
e := toml.NewEncoder(&confBuffer)
|
||||||
|
err := e.Encode(tlsConf)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
_, err = f.Write(confBuffer.Bytes())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,25 @@ func BodyContains(values ...string) ResponseCondition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BodyNotContains returns a retry condition function.
|
||||||
|
// The condition returns an error if the request body contain one of the given
|
||||||
|
// strings.
|
||||||
|
func BodyNotContains(values ...string) ResponseCondition {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read response body: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range values {
|
||||||
|
if strings.Contains(string(body), value) {
|
||||||
|
return fmt.Errorf("find '%s' in body '%s'", value, string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BodyContainsOr returns a retry condition function.
|
// BodyContainsOr returns a retry condition function.
|
||||||
// The condition returns an error if the request body does not contain one of the given
|
// The condition returns an error if the request body does not contain one of the given
|
||||||
// strings.
|
// strings.
|
||||||
|
|
|
@ -24,14 +24,16 @@ type HeaderStruct struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHeaderFromStruct constructs a new header instance from supplied frontend header struct.
|
// NewHeaderFromStruct constructs a new header instance from supplied frontend header struct.
|
||||||
func NewHeaderFromStruct(headers types.Headers) *HeaderStruct {
|
func NewHeaderFromStruct(headers *types.Headers) *HeaderStruct {
|
||||||
o := HeaderOptions{
|
if headers == nil || !headers.HasCustomHeadersDefined() {
|
||||||
CustomRequestHeaders: headers.CustomRequestHeaders,
|
return nil
|
||||||
CustomResponseHeaders: headers.CustomResponseHeaders,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HeaderStruct{
|
return &HeaderStruct{
|
||||||
opt: o,
|
opt: HeaderOptions{
|
||||||
|
CustomRequestHeaders: headers.CustomRequestHeaders,
|
||||||
|
CustomResponseHeaders: headers.CustomResponseHeaders,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,16 +17,14 @@ var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// newHeader constructs a new header instance with supplied options.
|
// newHeader constructs a new header instance with supplied options.
|
||||||
func newHeader(options ...HeaderOptions) *HeaderStruct {
|
func newHeader(options ...HeaderOptions) *HeaderStruct {
|
||||||
var o HeaderOptions
|
var opt HeaderOptions
|
||||||
if len(options) == 0 {
|
if len(options) == 0 {
|
||||||
o = HeaderOptions{}
|
opt = HeaderOptions{}
|
||||||
} else {
|
} else {
|
||||||
o = options[0]
|
opt = options[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HeaderStruct{
|
return &HeaderStruct{opt: opt}
|
||||||
opt: o,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoConfig(t *testing.T) {
|
func TestNoConfig(t *testing.T) {
|
||||||
|
|
|
@ -6,7 +6,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSecure constructs a new Secure instance with supplied options.
|
// NewSecure constructs a new Secure instance with supplied options.
|
||||||
func NewSecure(headers types.Headers) *secure.Secure {
|
func NewSecure(headers *types.Headers) *secure.Secure {
|
||||||
|
if headers == nil || !headers.HasSecureHeadersDefined() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
opt := secure.Options{
|
opt := secure.Options{
|
||||||
AllowedHosts: headers.AllowedHosts,
|
AllowedHosts: headers.AllowedHosts,
|
||||||
HostsProxyHeaders: headers.HostsProxyHeaders,
|
HostsProxyHeaders: headers.HostsProxyHeaders,
|
||||||
|
|
37
mkdocs.yml
37
mkdocs.yml
|
@ -7,24 +7,14 @@ dev_addr: 0.0.0.0:8000
|
||||||
repo_name: 'GitHub'
|
repo_name: 'GitHub'
|
||||||
repo_url: 'https://github.com/containous/traefik'
|
repo_url: 'https://github.com/containous/traefik'
|
||||||
|
|
||||||
# Documentation
|
|
||||||
docs_dir: 'docs'
|
docs_dir: 'docs'
|
||||||
|
|
||||||
#theme: united
|
theme:
|
||||||
#theme: readthedocs
|
name: 'material'
|
||||||
theme: 'material'
|
custom_dir: 'docs/theme'
|
||||||
#theme: bootstrap
|
language: en
|
||||||
|
include_sidebar: true
|
||||||
site_favicon: 'img/traefik.icon.png'
|
favicon: img/traefik.icon.png
|
||||||
|
|
||||||
copyright: "Copyright © 2016-2017 Containous SAS"
|
|
||||||
|
|
||||||
google_analytics:
|
|
||||||
- 'UA-51880359-3'
|
|
||||||
- 'docs.traefik.io'
|
|
||||||
|
|
||||||
# Options
|
|
||||||
extra:
|
|
||||||
logo: img/traefik.logo.png
|
logo: img/traefik.logo.png
|
||||||
palette:
|
palette:
|
||||||
primary: 'blue'
|
primary: 'blue'
|
||||||
|
@ -37,7 +27,16 @@ extra:
|
||||||
i18n:
|
i18n:
|
||||||
prev: 'Previous'
|
prev: 'Previous'
|
||||||
next: 'Next'
|
next: 'Next'
|
||||||
|
|
||||||
|
copyright: "Copyright © 2016-2018 Containous SAS"
|
||||||
|
|
||||||
|
google_analytics:
|
||||||
|
- 'UA-51880359-3'
|
||||||
|
- 'docs.traefik.io'
|
||||||
|
|
||||||
|
# Options
|
||||||
# Comment because the call of the CDN is very slow.
|
# Comment because the call of the CDN is very slow.
|
||||||
|
#extra:
|
||||||
# social:
|
# social:
|
||||||
# - type: 'github'
|
# - type: 'github'
|
||||||
# link: 'https://github.com/containous/traefik'
|
# link: 'https://github.com/containous/traefik'
|
||||||
|
@ -48,8 +47,6 @@ extra:
|
||||||
# - type: 'twitter'
|
# - type: 'twitter'
|
||||||
# link: 'https://twitter.com/traefikproxy'
|
# link: 'https://twitter.com/traefikproxy'
|
||||||
|
|
||||||
theme_dir: docs/theme/
|
|
||||||
|
|
||||||
extra_css:
|
extra_css:
|
||||||
- theme/styles/extra.css
|
- theme/styles/extra.css
|
||||||
- theme/styles/atom-one-light.css
|
- theme/styles/atom-one-light.css
|
||||||
|
@ -60,8 +57,8 @@ extra_javascript:
|
||||||
|
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- admonition
|
- admonition
|
||||||
# - codehilite(guess_lang=false)
|
- toc:
|
||||||
- toc(permalink=true)
|
permalink: true
|
||||||
|
|
||||||
# Page tree
|
# Page tree
|
||||||
pages:
|
pages:
|
||||||
|
|
|
@ -21,9 +21,9 @@ func (p *CatalogProvider) buildConfiguration(catalog []catalogUpdate) *types.Con
|
||||||
"getBackendName": getBackendName,
|
"getBackendName": getBackendName,
|
||||||
"getBackendAddress": getBackendAddress,
|
"getBackendAddress": getBackendAddress,
|
||||||
"getBasicAuth": p.getBasicAuth,
|
"getBasicAuth": p.getBasicAuth,
|
||||||
"getSticky": getSticky,
|
"getSticky": p.getSticky,
|
||||||
"hasStickinessLabel": hasStickinessLabel,
|
"hasStickinessLabel": p.hasStickinessLabel,
|
||||||
"getStickinessCookieName": getStickinessCookieName,
|
"getStickinessCookieName": p.getStickinessCookieName,
|
||||||
"getAttribute": p.getAttribute,
|
"getAttribute": p.getAttribute,
|
||||||
"getTag": getTag,
|
"getTag": getTag,
|
||||||
"hasTag": hasTag,
|
"hasTag": hasTag,
|
||||||
|
@ -147,8 +147,8 @@ func getBackendName(node *api.ServiceEntry, index int) string {
|
||||||
|
|
||||||
// TODO: Deprecated
|
// TODO: Deprecated
|
||||||
// Deprecated replaced by Stickiness
|
// Deprecated replaced by Stickiness
|
||||||
func getSticky(tags []string) string {
|
func (p *CatalogProvider) getSticky(tags []string) string {
|
||||||
stickyTag := getTag(label.TraefikBackendLoadBalancerSticky, tags, "")
|
stickyTag := p.getAttribute(label.SuffixBackendLoadBalancerSticky, tags, "")
|
||||||
if len(stickyTag) > 0 {
|
if len(stickyTag) > 0 {
|
||||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
|
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
|
||||||
} else {
|
} else {
|
||||||
|
@ -157,11 +157,11 @@ func getSticky(tags []string) string {
|
||||||
return stickyTag
|
return stickyTag
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasStickinessLabel(tags []string) bool {
|
func (p *CatalogProvider) hasStickinessLabel(tags []string) bool {
|
||||||
stickinessTag := getTag(label.TraefikBackendLoadBalancerStickiness, tags, "")
|
stickinessTag := p.getAttribute(label.SuffixBackendLoadBalancerStickiness, tags, "")
|
||||||
return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
|
return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStickinessCookieName(tags []string) string {
|
func (p *CatalogProvider) getStickinessCookieName(tags []string) string {
|
||||||
return getTag(label.TraefikBackendLoadBalancerStickinessCookieName, tags, "")
|
return p.getAttribute(label.SuffixBackendLoadBalancerStickinessCookieName, tags, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1006,6 +1006,10 @@ func TestGetBasicAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasStickinessLabel(t *testing.T) {
|
func TestHasStickinessLabel(t *testing.T) {
|
||||||
|
p := &CatalogProvider{
|
||||||
|
Prefix: "traefik",
|
||||||
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
tags []string
|
tags []string
|
||||||
|
@ -1037,7 +1041,7 @@ func TestHasStickinessLabel(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
actual := hasStickinessLabel(test.tags)
|
actual := p.hasStickinessLabel(test.tags)
|
||||||
assert.Equal(t, test.expected, actual)
|
assert.Equal(t, test.expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
|
||||||
"getRateLimitsExtractorFunc": getFuncStringLabel(label.TraefikFrontendRateLimitExtractorFunc, ""),
|
"getRateLimitsExtractorFunc": getFuncStringLabel(label.TraefikFrontendRateLimitExtractorFunc, ""),
|
||||||
"getRateLimits": getRateLimits,
|
"getRateLimits": getRateLimits,
|
||||||
// Headers
|
// Headers
|
||||||
|
"hasHeaders": hasHeaders,
|
||||||
"hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders),
|
"hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders),
|
||||||
"getRequestHeaders": getFuncMapLabel(label.TraefikFrontendRequestHeaders),
|
"getRequestHeaders": getFuncMapLabel(label.TraefikFrontendRequestHeaders),
|
||||||
"hasResponseHeaders": hasFunc(label.TraefikFrontendResponseHeaders),
|
"hasResponseHeaders": hasFunc(label.TraefikFrontendResponseHeaders),
|
||||||
|
|
|
@ -187,6 +187,15 @@ func getRateLimits(container dockerData) map[string]*types.Rate {
|
||||||
return label.ParseRateSets(container.Labels, prefix, label.RegexpFrontendRateLimit)
|
return label.ParseRateSets(container.Labels, prefix, label.RegexpFrontendRateLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasHeaders(container dockerData) bool {
|
||||||
|
for key := range container.Labels {
|
||||||
|
if strings.HasPrefix(key, label.TraefikFrontendHeaders) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Label functions
|
// Label functions
|
||||||
|
|
||||||
func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {
|
func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {
|
||||||
|
|
|
@ -75,9 +75,9 @@ func checkServiceLabelPort(container dockerData) error {
|
||||||
// Extract backend from labels for a given service and a given docker container
|
// Extract backend from labels for a given service and a given docker container
|
||||||
func getServiceBackend(container dockerData, serviceName string) string {
|
func getServiceBackend(container dockerData, serviceName string) string {
|
||||||
if value, ok := getServiceLabels(container, serviceName)[label.SuffixFrontendBackend]; ok {
|
if value, ok := getServiceLabels(container, serviceName)[label.SuffixFrontendBackend]; ok {
|
||||||
return container.ServiceName + "-" + value
|
return provider.Normalize(container.ServiceName + "-" + value)
|
||||||
}
|
}
|
||||||
return strings.TrimPrefix(container.ServiceName, "/") + "-" + getBackend(container) + "-" + provider.Normalize(serviceName)
|
return provider.Normalize(container.ServiceName + "-" + getBackend(container) + "-" + serviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract port from labels for a given service and a given docker container
|
// Extract port from labels for a given service and a given docker container
|
||||||
|
|
|
@ -402,12 +402,22 @@ func TestDockerGetServiceBackend(t *testing.T) {
|
||||||
})),
|
})),
|
||||||
expected: "fake-another-backend-myservice",
|
expected: "fake-another-backend-myservice",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
container: containerJSON(name("foo.bar")),
|
||||||
|
expected: "foo-bar-foo-bar-myservice",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
container: containerJSON(labels(map[string]string{
|
container: containerJSON(labels(map[string]string{
|
||||||
"traefik.myservice.frontend.backend": "custom-backend",
|
"traefik.myservice.frontend.backend": "custom-backend",
|
||||||
})),
|
})),
|
||||||
expected: "fake-custom-backend",
|
expected: "fake-custom-backend",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
container: containerJSON(labels(map[string]string{
|
||||||
|
label.TraefikBackend: "another.backend",
|
||||||
|
})),
|
||||||
|
expected: "fake-another-backend-myservice",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for containerID, test := range testCases {
|
for containerID, test := range testCases {
|
||||||
|
|
|
@ -126,7 +126,10 @@ func sendConfigToChannel(configurationChan chan<- types.ConfigMessage, configura
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadFileConfig(filename string) (*types.Configuration, error) {
|
func loadFileConfig(filename string) (*types.Configuration, error) {
|
||||||
configuration := new(types.Configuration)
|
configuration := &types.Configuration{
|
||||||
|
Frontends: make(map[string]*types.Frontend),
|
||||||
|
Backends: make(map[string]*types.Backend),
|
||||||
|
}
|
||||||
if _, err := toml.DecodeFile(filename, configuration); err != nil {
|
if _, err := toml.DecodeFile(filename, configuration); err != nil {
|
||||||
return nil, fmt.Errorf("error reading configuration file: %s", err)
|
return nil, fmt.Errorf("error reading configuration file: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -142,9 +145,8 @@ func loadFileConfigFromDirectory(directory string, configuration *types.Configur
|
||||||
|
|
||||||
if configuration == nil {
|
if configuration == nil {
|
||||||
configuration = &types.Configuration{
|
configuration = &types.Configuration{
|
||||||
Frontends: make(map[string]*types.Frontend),
|
Frontends: make(map[string]*types.Frontend),
|
||||||
Backends: make(map[string]*types.Backend),
|
Backends: make(map[string]*types.Backend),
|
||||||
TLSConfiguration: make([]*tls.Configuration, 0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,12 @@ func priority(value int) func(*types.Frontend) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func headers() func(*types.Frontend) {
|
||||||
|
return func(f *types.Frontend) {
|
||||||
|
f.Headers = &types.Headers{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func redirectEntryPoint(name string) func(*types.Frontend) {
|
func redirectEntryPoint(name string) func(*types.Frontend) {
|
||||||
return func(f *types.Frontend) {
|
return func(f *types.Frontend) {
|
||||||
if f.Redirect == nil {
|
if f.Redirect == nil {
|
||||||
|
|
|
@ -212,7 +212,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
||||||
|
|
||||||
priority := label.GetIntValue(i.Annotations, label.TraefikFrontendPriority, 0)
|
priority := label.GetIntValue(i.Annotations, label.TraefikFrontendPriority, 0)
|
||||||
|
|
||||||
headers := types.Headers{
|
headers := &types.Headers{
|
||||||
CustomRequestHeaders: label.GetMapValue(i.Annotations, annotationKubernetesCustomRequestHeaders),
|
CustomRequestHeaders: label.GetMapValue(i.Annotations, annotationKubernetesCustomRequestHeaders),
|
||||||
CustomResponseHeaders: label.GetMapValue(i.Annotations, annotationKubernetesCustomResponseHeaders),
|
CustomResponseHeaders: label.GetMapValue(i.Annotations, annotationKubernetesCustomResponseHeaders),
|
||||||
AllowedHosts: label.GetSliceStringValue(i.Annotations, annotationKubernetesAllowedHosts),
|
AllowedHosts: label.GetSliceStringValue(i.Annotations, annotationKubernetesAllowedHosts),
|
||||||
|
|
|
@ -137,18 +137,21 @@ func TestLoadIngresses(t *testing.T) {
|
||||||
),
|
),
|
||||||
frontends(
|
frontends(
|
||||||
frontend("foo/bar",
|
frontend("foo/bar",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(
|
routes(
|
||||||
route("/bar", "PathPrefix:/bar"),
|
route("/bar", "PathPrefix:/bar"),
|
||||||
route("foo", "Host:foo")),
|
route("foo", "Host:foo")),
|
||||||
),
|
),
|
||||||
frontend("foo/namedthing",
|
frontend("foo/namedthing",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(
|
routes(
|
||||||
route("/namedthing", "PathPrefix:/namedthing"),
|
route("/namedthing", "PathPrefix:/namedthing"),
|
||||||
route("foo", "Host:foo")),
|
route("foo", "Host:foo")),
|
||||||
),
|
),
|
||||||
frontend("bar",
|
frontend("bar",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(route("bar", "Host:bar")),
|
routes(route("bar", "Host:bar")),
|
||||||
),
|
),
|
||||||
|
@ -222,6 +225,7 @@ func TestRuleType(t *testing.T) {
|
||||||
require.NoError(t, err, "error loading ingresses")
|
require.NoError(t, err, "error loading ingresses")
|
||||||
|
|
||||||
expected := buildFrontends(frontend("host/path",
|
expected := buildFrontends(frontend("host/path",
|
||||||
|
headers(),
|
||||||
routes(
|
routes(
|
||||||
route("/path", fmt.Sprintf("%s:/path", test.frontendRuleType)),
|
route("/path", fmt.Sprintf("%s:/path", test.frontendRuleType)),
|
||||||
route("host", "Host:host")),
|
route("host", "Host:host")),
|
||||||
|
@ -267,6 +271,7 @@ func TestGetPassHostHeader(t *testing.T) {
|
||||||
backends(backend("foo/bar", lbMethod("wrr"), servers())),
|
backends(backend("foo/bar", lbMethod("wrr"), servers())),
|
||||||
frontends(
|
frontends(
|
||||||
frontend("foo/bar",
|
frontend("foo/bar",
|
||||||
|
headers(),
|
||||||
routes(
|
routes(
|
||||||
route("/bar", "PathPrefix:/bar"),
|
route("/bar", "PathPrefix:/bar"),
|
||||||
route("foo", "Host:foo")),
|
route("foo", "Host:foo")),
|
||||||
|
@ -310,6 +315,7 @@ func TestGetPassTLSCert(t *testing.T) {
|
||||||
expected := buildConfiguration(
|
expected := buildConfiguration(
|
||||||
backends(backend("foo/bar", lbMethod("wrr"), servers())),
|
backends(backend("foo/bar", lbMethod("wrr"), servers())),
|
||||||
frontends(frontend("foo/bar",
|
frontends(frontend("foo/bar",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
passTLSCert(),
|
passTLSCert(),
|
||||||
routes(
|
routes(
|
||||||
|
@ -364,6 +370,7 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) {
|
||||||
expected := buildConfiguration(
|
expected := buildConfiguration(
|
||||||
backends(backend("foo", lbMethod("wrr"), servers())),
|
backends(backend("foo", lbMethod("wrr"), servers())),
|
||||||
frontends(frontend("foo",
|
frontends(frontend("foo",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(route("foo", "Host:foo")),
|
routes(route("foo", "Host:foo")),
|
||||||
)),
|
)),
|
||||||
|
@ -405,7 +412,9 @@ func TestHostlessIngress(t *testing.T) {
|
||||||
|
|
||||||
expected := buildConfiguration(
|
expected := buildConfiguration(
|
||||||
backends(backend("/bar", lbMethod("wrr"), servers())),
|
backends(backend("/bar", lbMethod("wrr"), servers())),
|
||||||
frontends(frontend("/bar", routes(route("/bar", "PathPrefix:/bar")))),
|
frontends(frontend("/bar",
|
||||||
|
headers(),
|
||||||
|
routes(route("/bar", "PathPrefix:/bar")))),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.Equal(t, expected, actual)
|
assert.Equal(t, expected, actual)
|
||||||
|
@ -503,12 +512,14 @@ func TestServiceAnnotations(t *testing.T) {
|
||||||
),
|
),
|
||||||
frontends(
|
frontends(
|
||||||
frontend("foo/bar",
|
frontend("foo/bar",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(
|
routes(
|
||||||
route("/bar", "PathPrefix:/bar"),
|
route("/bar", "PathPrefix:/bar"),
|
||||||
route("foo", "Host:foo")),
|
route("foo", "Host:foo")),
|
||||||
),
|
),
|
||||||
frontend("bar",
|
frontend("bar",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(route("bar", "Host:bar"))),
|
routes(route("bar", "Host:bar"))),
|
||||||
),
|
),
|
||||||
|
@ -712,17 +723,20 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
),
|
),
|
||||||
frontends(
|
frontends(
|
||||||
frontend("foo/bar",
|
frontend("foo/bar",
|
||||||
|
headers(),
|
||||||
routes(
|
routes(
|
||||||
route("/bar", "PathPrefix:/bar"),
|
route("/bar", "PathPrefix:/bar"),
|
||||||
route("foo", "Host:foo")),
|
route("foo", "Host:foo")),
|
||||||
),
|
),
|
||||||
frontend("other/stuff",
|
frontend("other/stuff",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(
|
routes(
|
||||||
route("/stuff", "PathPrefix:/stuff"),
|
route("/stuff", "PathPrefix:/stuff"),
|
||||||
route("other", "Host:other")),
|
route("other", "Host:other")),
|
||||||
),
|
),
|
||||||
frontend("other/",
|
frontend("other/",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
entryPoints("http", "https"),
|
entryPoints("http", "https"),
|
||||||
routes(
|
routes(
|
||||||
|
@ -730,6 +744,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
route("other", "Host:other")),
|
route("other", "Host:other")),
|
||||||
),
|
),
|
||||||
frontend("other/sslstuff",
|
frontend("other/sslstuff",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
passTLSCert(),
|
passTLSCert(),
|
||||||
routes(
|
routes(
|
||||||
|
@ -737,6 +752,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
route("other", "Host:other")),
|
route("other", "Host:other")),
|
||||||
),
|
),
|
||||||
frontend("other/sslstuff",
|
frontend("other/sslstuff",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
passTLSCert(),
|
passTLSCert(),
|
||||||
routes(
|
routes(
|
||||||
|
@ -744,6 +760,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
route("other", "Host:other")),
|
route("other", "Host:other")),
|
||||||
),
|
),
|
||||||
frontend("basic/auth",
|
frontend("basic/auth",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
basicAuth("myUser:myEncodedPW"),
|
basicAuth("myUser:myEncodedPW"),
|
||||||
routes(
|
routes(
|
||||||
|
@ -751,6 +768,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
route("basic", "Host:basic")),
|
route("basic", "Host:basic")),
|
||||||
),
|
),
|
||||||
frontend("redirect/https",
|
frontend("redirect/https",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
redirectEntryPoint("https"),
|
redirectEntryPoint("https"),
|
||||||
routes(
|
routes(
|
||||||
|
@ -758,6 +776,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
route("redirect", "Host:redirect")),
|
route("redirect", "Host:redirect")),
|
||||||
),
|
),
|
||||||
frontend("test/whitelist-source-range",
|
frontend("test/whitelist-source-range",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
whitelistSourceRange("1.1.1.1/24", "1234:abcd::42/32"),
|
whitelistSourceRange("1.1.1.1/24", "1234:abcd::42/32"),
|
||||||
routes(
|
routes(
|
||||||
|
@ -765,6 +784,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
route("test", "Host:test")),
|
route("test", "Host:test")),
|
||||||
),
|
),
|
||||||
frontend("rewrite/api",
|
frontend("rewrite/api",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(
|
routes(
|
||||||
route("/api", "PathPrefix:/api;ReplacePath:/"),
|
route("/api", "PathPrefix:/api;ReplacePath:/"),
|
||||||
|
@ -824,6 +844,7 @@ func TestPriorityHeaderValue(t *testing.T) {
|
||||||
),
|
),
|
||||||
frontends(
|
frontends(
|
||||||
frontend("foo/bar",
|
frontend("foo/bar",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
priority(1337),
|
priority(1337),
|
||||||
routes(
|
routes(
|
||||||
|
@ -882,6 +903,7 @@ func TestInvalidPassTLSCertValue(t *testing.T) {
|
||||||
),
|
),
|
||||||
frontends(
|
frontends(
|
||||||
frontend("foo/bar",
|
frontend("foo/bar",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(
|
routes(
|
||||||
route("/bar", "PathPrefix:/bar"),
|
route("/bar", "PathPrefix:/bar"),
|
||||||
|
@ -939,6 +961,7 @@ func TestInvalidPassHostHeaderValue(t *testing.T) {
|
||||||
),
|
),
|
||||||
frontends(
|
frontends(
|
||||||
frontend("foo/bar",
|
frontend("foo/bar",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(
|
routes(
|
||||||
route("/bar", "PathPrefix:/bar"),
|
route("/bar", "PathPrefix:/bar"),
|
||||||
|
@ -1112,14 +1135,17 @@ func TestMissingResources(t *testing.T) {
|
||||||
),
|
),
|
||||||
frontends(
|
frontends(
|
||||||
frontend("fully_working",
|
frontend("fully_working",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(route("fully_working", "Host:fully_working")),
|
routes(route("fully_working", "Host:fully_working")),
|
||||||
),
|
),
|
||||||
frontend("missing_endpoints",
|
frontend("missing_endpoints",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(route("missing_endpoints", "Host:missing_endpoints")),
|
routes(route("missing_endpoints", "Host:missing_endpoints")),
|
||||||
),
|
),
|
||||||
frontend("missing_endpoint_subsets",
|
frontend("missing_endpoint_subsets",
|
||||||
|
headers(),
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(route("missing_endpoint_subsets", "Host:missing_endpoint_subsets")),
|
routes(route("missing_endpoint_subsets", "Host:missing_endpoint_subsets")),
|
||||||
),
|
),
|
||||||
|
|
|
@ -27,26 +27,27 @@ const (
|
||||||
SuffixFrontendAuthBasic = "frontend.auth.basic"
|
SuffixFrontendAuthBasic = "frontend.auth.basic"
|
||||||
SuffixFrontendBackend = "frontend.backend"
|
SuffixFrontendBackend = "frontend.backend"
|
||||||
SuffixFrontendEntryPoints = "frontend.entryPoints"
|
SuffixFrontendEntryPoints = "frontend.entryPoints"
|
||||||
SuffixFrontendRequestHeaders = "frontend.headers.customRequestHeaders"
|
SuffixFrontendHeaders = "frontend.headers."
|
||||||
SuffixFrontendResponseHeaders = "frontend.headers.customResponseHeaders"
|
SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders"
|
||||||
SuffixFrontendHeadersAllowedHosts = "frontend.headers.allowedHosts"
|
SuffixFrontendResponseHeaders = SuffixFrontendHeaders + "customResponseHeaders"
|
||||||
SuffixFrontendHeadersHostsProxyHeaders = "frontend.headers.hostsProxyHeaders"
|
SuffixFrontendHeadersAllowedHosts = SuffixFrontendHeaders + "allowedHosts"
|
||||||
SuffixFrontendHeadersSSLRedirect = "frontend.headers.SSLRedirect"
|
SuffixFrontendHeadersHostsProxyHeaders = SuffixFrontendHeaders + "hostsProxyHeaders"
|
||||||
SuffixFrontendHeadersSSLTemporaryRedirect = "frontend.headers.SSLTemporaryRedirect"
|
SuffixFrontendHeadersSSLRedirect = SuffixFrontendHeaders + "SSLRedirect"
|
||||||
SuffixFrontendHeadersSSLHost = "frontend.headers.SSLHost"
|
SuffixFrontendHeadersSSLTemporaryRedirect = SuffixFrontendHeaders + "SSLTemporaryRedirect"
|
||||||
SuffixFrontendHeadersSSLProxyHeaders = "frontend.headers.SSLProxyHeaders"
|
SuffixFrontendHeadersSSLHost = SuffixFrontendHeaders + "SSLHost"
|
||||||
SuffixFrontendHeadersSTSSeconds = "frontend.headers.STSSeconds"
|
SuffixFrontendHeadersSSLProxyHeaders = SuffixFrontendHeaders + "SSLProxyHeaders"
|
||||||
SuffixFrontendHeadersSTSIncludeSubdomains = "frontend.headers.STSIncludeSubdomains"
|
SuffixFrontendHeadersSTSSeconds = SuffixFrontendHeaders + "STSSeconds"
|
||||||
SuffixFrontendHeadersSTSPreload = "frontend.headers.STSPreload"
|
SuffixFrontendHeadersSTSIncludeSubdomains = SuffixFrontendHeaders + "STSIncludeSubdomains"
|
||||||
SuffixFrontendHeadersForceSTSHeader = "frontend.headers.forceSTSHeader"
|
SuffixFrontendHeadersSTSPreload = SuffixFrontendHeaders + "STSPreload"
|
||||||
SuffixFrontendHeadersFrameDeny = "frontend.headers.frameDeny"
|
SuffixFrontendHeadersForceSTSHeader = SuffixFrontendHeaders + "forceSTSHeader"
|
||||||
SuffixFrontendHeadersCustomFrameOptionsValue = "frontend.headers.customFrameOptionsValue"
|
SuffixFrontendHeadersFrameDeny = SuffixFrontendHeaders + "frameDeny"
|
||||||
SuffixFrontendHeadersContentTypeNosniff = "frontend.headers.contentTypeNosniff"
|
SuffixFrontendHeadersCustomFrameOptionsValue = SuffixFrontendHeaders + "customFrameOptionsValue"
|
||||||
SuffixFrontendHeadersBrowserXSSFilter = "frontend.headers.browserXSSFilter"
|
SuffixFrontendHeadersContentTypeNosniff = SuffixFrontendHeaders + "contentTypeNosniff"
|
||||||
SuffixFrontendHeadersContentSecurityPolicy = "frontend.headers.contentSecurityPolicy"
|
SuffixFrontendHeadersBrowserXSSFilter = SuffixFrontendHeaders + "browserXSSFilter"
|
||||||
SuffixFrontendHeadersPublicKey = "frontend.headers.publicKey"
|
SuffixFrontendHeadersContentSecurityPolicy = SuffixFrontendHeaders + "contentSecurityPolicy"
|
||||||
SuffixFrontendHeadersReferrerPolicy = "frontend.headers.referrerPolicy"
|
SuffixFrontendHeadersPublicKey = SuffixFrontendHeaders + "publicKey"
|
||||||
SuffixFrontendHeadersIsDevelopment = "frontend.headers.isDevelopment"
|
SuffixFrontendHeadersReferrerPolicy = SuffixFrontendHeaders + "referrerPolicy"
|
||||||
|
SuffixFrontendHeadersIsDevelopment = SuffixFrontendHeaders + "isDevelopment"
|
||||||
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
|
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
|
||||||
SuffixFrontendPassTLSCert = "frontend.passTLSCert"
|
SuffixFrontendPassTLSCert = "frontend.passTLSCert"
|
||||||
SuffixFrontendPriority = "frontend.priority"
|
SuffixFrontendPriority = "frontend.priority"
|
||||||
|
@ -92,6 +93,7 @@ const (
|
||||||
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
|
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
|
||||||
TraefikFrontendValue = Prefix + SuffixFrontendValue
|
TraefikFrontendValue = Prefix + SuffixFrontendValue
|
||||||
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
|
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
|
||||||
|
TraefikFrontendHeaders = Prefix + SuffixFrontendHeaders
|
||||||
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
|
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
|
||||||
TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders
|
TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders
|
||||||
TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts
|
TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts
|
||||||
|
|
|
@ -64,6 +64,7 @@ func (p *Provider) buildConfiguration() *types.Configuration {
|
||||||
"getRateLimitsExtractorFunc": getFuncStringService(label.SuffixFrontendRateLimitExtractorFunc, ""),
|
"getRateLimitsExtractorFunc": getFuncStringService(label.SuffixFrontendRateLimitExtractorFunc, ""),
|
||||||
"getRateLimits": getRateLimits,
|
"getRateLimits": getRateLimits,
|
||||||
// Headers
|
// Headers
|
||||||
|
"hasHeaders": hasPrefixFuncService(label.TraefikFrontendHeaders),
|
||||||
"hasRequestHeaders": hasFuncService(label.SuffixFrontendRequestHeaders),
|
"hasRequestHeaders": hasFuncService(label.SuffixFrontendRequestHeaders),
|
||||||
"getRequestHeaders": getFuncMapService(label.SuffixFrontendRequestHeaders),
|
"getRequestHeaders": getFuncMapService(label.SuffixFrontendRequestHeaders),
|
||||||
"hasResponseHeaders": hasFuncService(label.SuffixFrontendResponseHeaders),
|
"hasResponseHeaders": hasFuncService(label.SuffixFrontendResponseHeaders),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mkdocs==0.16.3
|
mkdocs>=0.17.2
|
||||||
pymdown-extensions>=1.4
|
pymdown-extensions>=1.4
|
||||||
mkdocs-bootswatch>=0.4.0
|
mkdocs-bootswatch>=0.4.0
|
||||||
mkdocs-material==1.12.2
|
mkdocs-material>=2.2.6
|
||||||
|
|
|
@ -439,12 +439,12 @@ func (s *Server) loadConfiguration(configMsg types.ConfigMessage) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
|
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
|
||||||
s.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
|
s.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
|
||||||
if newServerEntryPoint.certs.Get() != nil {
|
if s.globalConfiguration.EntryPoints[newServerEntryPointName].TLS == nil {
|
||||||
if s.globalConfiguration.EntryPoints[newServerEntryPointName].TLS == nil {
|
if newServerEntryPoint.certs.Get() != nil {
|
||||||
log.Debugf("Certificates not added to non-TLS entryPoint %s.", newServerEntryPointName)
|
log.Debugf("Certificates not added to non-TLS entryPoint %s.", newServerEntryPointName)
|
||||||
} else {
|
|
||||||
s.serverEntryPoints[newServerEntryPointName].certs.Set(newServerEntryPoint.certs.Get())
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
s.serverEntryPoints[newServerEntryPointName].certs.Set(newServerEntryPoint.certs.Get())
|
||||||
}
|
}
|
||||||
log.Infof("Server configuration reloaded on %s", s.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
|
log.Infof("Server configuration reloaded on %s", s.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
|
||||||
}
|
}
|
||||||
|
@ -980,10 +980,9 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
|
||||||
continue frontend
|
continue frontend
|
||||||
}
|
}
|
||||||
|
|
||||||
var headerMiddleware *middlewares.HeaderStruct
|
headerMiddleware := middlewares.NewHeaderFromStruct(frontend.Headers)
|
||||||
var responseModifier func(res *http.Response) error
|
var responseModifier func(res *http.Response) error
|
||||||
if frontend.Headers.HasCustomHeadersDefined() {
|
if headerMiddleware != nil {
|
||||||
headerMiddleware = middlewares.NewHeaderFromStruct(frontend.Headers)
|
|
||||||
responseModifier = headerMiddleware.ModifyResponseHeaders
|
responseModifier = headerMiddleware.ModifyResponseHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1166,8 +1165,9 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
|
||||||
log.Debugf("Adding header middleware for frontend %s", frontendName)
|
log.Debugf("Adding header middleware for frontend %s", frontendName)
|
||||||
n.Use(headerMiddleware)
|
n.Use(headerMiddleware)
|
||||||
}
|
}
|
||||||
if frontend.Headers.HasSecureHeadersDefined() {
|
|
||||||
secureMiddleware := middlewares.NewSecure(frontend.Headers)
|
secureMiddleware := middlewares.NewSecure(frontend.Headers)
|
||||||
|
if secureMiddleware != nil {
|
||||||
log.Debugf("Adding secure middleware for frontend %s", frontendName)
|
log.Debugf("Adding secure middleware for frontend %s", frontendName)
|
||||||
n.UseFunc(secureMiddleware.HandlerFuncWithNext)
|
n.UseFunc(secureMiddleware.HandlerFuncWithNext)
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,6 +179,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{ if hasHeaders $container}}
|
||||||
[frontends."frontend-{{$frontend}}".headers]
|
[frontends."frontend-{{$frontend}}".headers]
|
||||||
{{if hasSSLRedirectHeaders $container}}
|
{{if hasSSLRedirectHeaders $container}}
|
||||||
SSLRedirect = {{getSSLRedirectHeaders $container}}
|
SSLRedirect = {{getSSLRedirectHeaders $container}}
|
||||||
|
@ -258,6 +259,7 @@
|
||||||
{{$k}} = "{{$v}}"
|
{{$k}} = "{{$v}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
||||||
rule = "{{getFrontendRule $container}}"
|
rule = "{{getFrontendRule $container}}"
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
replacement = "{{$frontend.RedirectReplacement}}"
|
replacement = "{{$frontend.RedirectReplacement}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if $frontend.Headers }}
|
||||||
[frontends."{{$frontendName}}".headers]
|
[frontends."{{$frontendName}}".headers]
|
||||||
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
|
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
|
||||||
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
|
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
|
||||||
|
@ -85,9 +86,10 @@
|
||||||
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
|
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
|
||||||
{{$k}} = "{{$v}}"
|
{{$k}} = "{{$v}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{range $routeName, $route := $frontend.Routes}}
|
{{range $routeName, $route := $frontend.Routes}}
|
||||||
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
||||||
rule = "{{$route.Rule}}"
|
rule = "{{$route.Rule}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
|
@ -103,6 +103,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if hasHeaders $app $serviceName }}
|
||||||
[frontends."{{ getFrontendName $app $serviceName }}".headers]
|
[frontends."{{ getFrontendName $app $serviceName }}".headers]
|
||||||
{{if hasSSLRedirectHeaders $app $serviceName}}
|
{{if hasSSLRedirectHeaders $app $serviceName}}
|
||||||
SSLRedirect = {{getSSLRedirectHeaders $app $serviceName}}
|
SSLRedirect = {{getSSLRedirectHeaders $app $serviceName}}
|
||||||
|
@ -182,6 +183,7 @@
|
||||||
{{$k}} = "{{$v}}"
|
{{$k}} = "{{$v}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
[frontends."{{ getFrontendName $app $serviceName }}".routes."route-host{{$app.ID | replace "/" "-"}}{{getServiceNameSuffix $serviceName }}"]
|
[frontends."{{ getFrontendName $app $serviceName }}".routes."route-host{{$app.ID | replace "/" "-"}}{{getServiceNameSuffix $serviceName }}"]
|
||||||
rule = "{{getFrontendRule $app $serviceName}}"
|
rule = "{{getFrontendRule $app $serviceName}}"
|
||||||
|
|
|
@ -113,14 +113,14 @@ type Headers struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasCustomHeadersDefined checks to see if any of the custom header elements have been set
|
// HasCustomHeadersDefined checks to see if any of the custom header elements have been set
|
||||||
func (h Headers) HasCustomHeadersDefined() bool {
|
func (h *Headers) HasCustomHeadersDefined() bool {
|
||||||
return len(h.CustomResponseHeaders) != 0 ||
|
return h != nil && (len(h.CustomResponseHeaders) != 0 ||
|
||||||
len(h.CustomRequestHeaders) != 0
|
len(h.CustomRequestHeaders) != 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasSecureHeadersDefined checks to see if any of the secure header elements have been set
|
// HasSecureHeadersDefined checks to see if any of the secure header elements have been set
|
||||||
func (h Headers) HasSecureHeadersDefined() bool {
|
func (h *Headers) HasSecureHeadersDefined() bool {
|
||||||
return len(h.AllowedHosts) != 0 ||
|
return h != nil && (len(h.AllowedHosts) != 0 ||
|
||||||
len(h.HostsProxyHeaders) != 0 ||
|
len(h.HostsProxyHeaders) != 0 ||
|
||||||
h.SSLRedirect ||
|
h.SSLRedirect ||
|
||||||
h.SSLTemporaryRedirect ||
|
h.SSLTemporaryRedirect ||
|
||||||
|
@ -137,7 +137,7 @@ func (h Headers) HasSecureHeadersDefined() bool {
|
||||||
h.ContentSecurityPolicy != "" ||
|
h.ContentSecurityPolicy != "" ||
|
||||||
h.PublicKey != "" ||
|
h.PublicKey != "" ||
|
||||||
h.ReferrerPolicy != "" ||
|
h.ReferrerPolicy != "" ||
|
||||||
h.IsDevelopment
|
h.IsDevelopment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frontend holds frontend configuration.
|
// Frontend holds frontend configuration.
|
||||||
|
@ -150,7 +150,7 @@ type Frontend struct {
|
||||||
Priority int `json:"priority"`
|
Priority int `json:"priority"`
|
||||||
BasicAuth []string `json:"basicAuth"`
|
BasicAuth []string `json:"basicAuth"`
|
||||||
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"`
|
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"`
|
||||||
Headers Headers `json:"headers,omitempty"`
|
Headers *Headers `json:"headers,omitempty"`
|
||||||
Errors map[string]*ErrorPage `json:"errors,omitempty"`
|
Errors map[string]*ErrorPage `json:"errors,omitempty"`
|
||||||
RateLimit *RateLimit `json:"ratelimit,omitempty"`
|
RateLimit *RateLimit `json:"ratelimit,omitempty"`
|
||||||
Redirect *Redirect `json:"redirect,omitempty"`
|
Redirect *Redirect `json:"redirect,omitempty"`
|
||||||
|
|
Loading…
Reference in a new issue