Merge branch 'v1.5' into master

This commit is contained in:
Fernandez Ludovic 2018-01-02 14:49:11 +01:00
commit c84fb9895e
35 changed files with 462 additions and 124 deletions

View file

@ -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)

View file

@ -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

View file

@ -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}}"

View file

@ -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
} }

View file

@ -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) {

View file

@ -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 |
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|

View file

@ -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>

View file

@ -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

View file

@ -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 |

View file

@ -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")
}

View file

@ -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"

View file

@ -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)
}
} }

View file

@ -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.

View file

@ -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,
},
} }
} }

View file

@ -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) {

View file

@ -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,

View file

@ -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 &copy; 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 &copy; 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:

View file

@ -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, "")
} }

View file

@ -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)
}) })
} }

View file

@ -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),

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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),
} }
} }

View file

@ -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 {

View file

@ -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),

View file

@ -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")),
), ),

View file

@ -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

View file

@ -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),

View file

@ -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

View file

@ -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)
} }

View file

@ -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}}"

View file

@ -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}}

View file

@ -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}}"

View file

@ -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"`