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
|
||||
|
||||
## [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)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc2...v1.5.0-rc3)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -304,6 +304,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
|
|||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ if hasHeaders $container}}
|
||||
[frontends."frontend-{{$frontend}}".headers]
|
||||
{{if hasSSLRedirectHeaders $container}}
|
||||
SSLRedirect = {{getSSLRedirectHeaders $container}}
|
||||
|
@ -383,6 +384,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
|
|||
{{$k}} = "{{$v}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
||||
rule = "{{getFrontendRule $container}}"
|
||||
|
@ -536,6 +538,7 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
|
|||
replacement = "{{$frontend.RedirectReplacement}}"
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Headers }}
|
||||
[frontends."{{$frontendName}}".headers]
|
||||
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
|
||||
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
|
||||
|
@ -579,13 +582,13 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
|
|||
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
|
||||
{{$k}} = "{{$v}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{range $routeName, $route := $frontend.Routes}}
|
||||
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
||||
rule = "{{$route.Rule}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
`)
|
||||
{{end}}`)
|
||||
|
||||
func templatesKubernetesTmplBytes() ([]byte, error) {
|
||||
return _templatesKubernetesTmpl, nil
|
||||
|
@ -805,6 +808,7 @@ var _templatesMarathonTmpl = []byte(`{{$apps := .Applications}}
|
|||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if hasHeaders $app $serviceName }}
|
||||
[frontends."{{ getFrontendName $app $serviceName }}".headers]
|
||||
{{if hasSSLRedirectHeaders $app $serviceName}}
|
||||
SSLRedirect = {{getSSLRedirectHeaders $app $serviceName}}
|
||||
|
@ -884,6 +888,7 @@ var _templatesMarathonTmpl = []byte(`{{$apps := .Applications}}
|
|||
{{$k}} = "{{$v}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends."{{ getFrontendName $app $serviceName }}".routes."route-host{{$app.ID | replace "/" "-"}}{{getServiceNameSuffix $serviceName }}"]
|
||||
rule = "{{getFrontendRule $app $serviceName}}"
|
||||
|
|
|
@ -84,7 +84,7 @@ Add more configuration information here.
|
|||
)
|
||||
|
||||
// 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
|
||||
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 {
|
||||
|
||||
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
|
||||
if err := getVersionPrint(&version); err != nil {
|
||||
return "", err
|
||||
|
@ -124,7 +124,7 @@ func createBugReport(traefikConfiguration interface{}) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
config, err := anonymize.Do(&traefikConfiguration, true)
|
||||
config, err := anonymize.Do(traefikConfiguration, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -7,16 +7,27 @@ import (
|
|||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_createBugReport(t *testing.T) {
|
||||
traefikConfiguration := TraefikConfiguration{
|
||||
traefikConfiguration := &TraefikConfiguration{
|
||||
ConfigFile: "FOO",
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"goo": &configuration.EntryPoint{
|
||||
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{
|
||||
|
@ -28,6 +39,11 @@ func Test_createBugReport(t *testing.T) {
|
|||
|
||||
report, err := createBugReport(traefikConfiguration)
|
||||
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) {
|
||||
|
|
|
@ -150,12 +150,15 @@ domain = "marathon.localhost"
|
|||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
|
||||
|
||||
## 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 |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
@ -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.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 |
|
||||
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||
|
|
4
docs/theme/partials/footer.html
vendored
4
docs/theme/partials/footer.html
vendored
|
@ -20,7 +20,7 @@
|
|||
IN THE SOFTWARE.
|
||||
-->
|
||||
|
||||
{% import "partials/language.html" as lang %}
|
||||
{% import "partials/language.html" as lang with context %}
|
||||
|
||||
<!-- Application footer -->
|
||||
<footer class="md-footer">
|
||||
|
@ -97,7 +97,7 @@
|
|||
|
||||
<!-- Social links -->
|
||||
{% block social %}
|
||||
{% include "partials/social.html" %}
|
||||
{% include "partials/social.html" %}
|
||||
{% endblock %}
|
||||
</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 `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
|
||||
|
||||
### Scenarios
|
||||
|
|
|
@ -86,7 +86,7 @@ docker $(docker-machine config mhs-demo0) run \
|
|||
-c /dev/null \
|
||||
--docker \
|
||||
--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.ca=/ssl/ca.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 |
|
||||
| `-c /dev/null` | empty config file |
|
||||
| `--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 |
|
||||
| `--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
|
||||
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]
|
||||
address = ":4443"
|
||||
[entryPoints.https.tls]
|
||||
[entryPoints.https02]
|
||||
address = ":8443"
|
||||
[entryPoints.https02.tls]
|
||||
|
||||
[web]
|
||||
address = ":8080"
|
||||
|
|
|
@ -353,7 +353,7 @@ func startTestServer(port string, statusCode int) (ts *httptest.Server) {
|
|||
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
|
||||
// that traefik routes the requests to the expected backends thanks to given certificate if possible
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
|
@ -479,7 +479,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
|
|||
req.Header.Set("Accept", "*/*")
|
||||
|
||||
// Change certificates configuration file content
|
||||
modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName)
|
||||
modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName, "https")
|
||||
var resp *http.Response
|
||||
err = try.Do(30*time.Second, func() error {
|
||||
resp, err = client.Do(req)
|
||||
|
@ -525,29 +525,121 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
|
|||
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound)
|
||||
}
|
||||
|
||||
// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file.
|
||||
func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName string) {
|
||||
tlsConf := types.Configuration{
|
||||
TLSConfiguration: []*traefikTls.Configuration{
|
||||
{
|
||||
Certificate: &traefikTls.Certificate{
|
||||
CertFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".cert"),
|
||||
KeyFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".key"),
|
||||
},
|
||||
},
|
||||
// TestWithSNIDynamicConfigRouteWithChangeForEmptyTlsConfiguration involves a client sending HTTPS requests with
|
||||
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
|
||||
// that traefik updates its configuration when the HTTPS configuration is modified, even if it totally deleted, and
|
||||
// it routes the requests to the expected backends thanks to given certificate if possible
|
||||
// otherwise thanks to the default one.
|
||||
func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c *check.C) {
|
||||
dynamicConfFileName := s.adaptFile(c, "fixtures/https/dynamic_https.toml", struct{}{})
|
||||
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)
|
||||
err := e.Encode(tlsConf)
|
||||
|
||||
// wait for Traefik
|
||||
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)
|
||||
|
||||
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)
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer func() {
|
||||
f.Close()
|
||||
}()
|
||||
f.Truncate(0)
|
||||
_, err = f.Write(confBuffer.Bytes())
|
||||
c.Assert(err, checker.IsNil)
|
||||
// If certificate file is not provided, just truncate the configuration file
|
||||
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.
|
||||
// The condition returns an error if the request body does not contain one of the given
|
||||
// strings.
|
||||
|
|
|
@ -24,14 +24,16 @@ type HeaderStruct struct {
|
|||
}
|
||||
|
||||
// NewHeaderFromStruct constructs a new header instance from supplied frontend header struct.
|
||||
func NewHeaderFromStruct(headers types.Headers) *HeaderStruct {
|
||||
o := HeaderOptions{
|
||||
CustomRequestHeaders: headers.CustomRequestHeaders,
|
||||
CustomResponseHeaders: headers.CustomResponseHeaders,
|
||||
func NewHeaderFromStruct(headers *types.Headers) *HeaderStruct {
|
||||
if headers == nil || !headers.HasCustomHeadersDefined() {
|
||||
return nil
|
||||
}
|
||||
|
||||
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.
|
||||
func newHeader(options ...HeaderOptions) *HeaderStruct {
|
||||
var o HeaderOptions
|
||||
var opt HeaderOptions
|
||||
if len(options) == 0 {
|
||||
o = HeaderOptions{}
|
||||
opt = HeaderOptions{}
|
||||
} else {
|
||||
o = options[0]
|
||||
opt = options[0]
|
||||
}
|
||||
|
||||
return &HeaderStruct{
|
||||
opt: o,
|
||||
}
|
||||
return &HeaderStruct{opt: opt}
|
||||
}
|
||||
|
||||
func TestNoConfig(t *testing.T) {
|
||||
|
|
|
@ -6,7 +6,11 @@ import (
|
|||
)
|
||||
|
||||
// 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{
|
||||
AllowedHosts: headers.AllowedHosts,
|
||||
HostsProxyHeaders: headers.HostsProxyHeaders,
|
||||
|
|
37
mkdocs.yml
37
mkdocs.yml
|
@ -7,24 +7,14 @@ dev_addr: 0.0.0.0:8000
|
|||
repo_name: 'GitHub'
|
||||
repo_url: 'https://github.com/containous/traefik'
|
||||
|
||||
# Documentation
|
||||
docs_dir: 'docs'
|
||||
|
||||
#theme: united
|
||||
#theme: readthedocs
|
||||
theme: 'material'
|
||||
#theme: bootstrap
|
||||
|
||||
site_favicon: 'img/traefik.icon.png'
|
||||
|
||||
copyright: "Copyright © 2016-2017 Containous SAS"
|
||||
|
||||
google_analytics:
|
||||
- 'UA-51880359-3'
|
||||
- 'docs.traefik.io'
|
||||
|
||||
# Options
|
||||
extra:
|
||||
theme:
|
||||
name: 'material'
|
||||
custom_dir: 'docs/theme'
|
||||
language: en
|
||||
include_sidebar: true
|
||||
favicon: img/traefik.icon.png
|
||||
logo: img/traefik.logo.png
|
||||
palette:
|
||||
primary: 'blue'
|
||||
|
@ -37,7 +27,16 @@ extra:
|
|||
i18n:
|
||||
prev: 'Previous'
|
||||
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.
|
||||
#extra:
|
||||
# social:
|
||||
# - type: 'github'
|
||||
# link: 'https://github.com/containous/traefik'
|
||||
|
@ -48,8 +47,6 @@ extra:
|
|||
# - type: 'twitter'
|
||||
# link: 'https://twitter.com/traefikproxy'
|
||||
|
||||
theme_dir: docs/theme/
|
||||
|
||||
extra_css:
|
||||
- theme/styles/extra.css
|
||||
- theme/styles/atom-one-light.css
|
||||
|
@ -60,8 +57,8 @@ extra_javascript:
|
|||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
# - codehilite(guess_lang=false)
|
||||
- toc(permalink=true)
|
||||
- toc:
|
||||
permalink: true
|
||||
|
||||
# Page tree
|
||||
pages:
|
||||
|
|
|
@ -21,9 +21,9 @@ func (p *CatalogProvider) buildConfiguration(catalog []catalogUpdate) *types.Con
|
|||
"getBackendName": getBackendName,
|
||||
"getBackendAddress": getBackendAddress,
|
||||
"getBasicAuth": p.getBasicAuth,
|
||||
"getSticky": getSticky,
|
||||
"hasStickinessLabel": hasStickinessLabel,
|
||||
"getStickinessCookieName": getStickinessCookieName,
|
||||
"getSticky": p.getSticky,
|
||||
"hasStickinessLabel": p.hasStickinessLabel,
|
||||
"getStickinessCookieName": p.getStickinessCookieName,
|
||||
"getAttribute": p.getAttribute,
|
||||
"getTag": getTag,
|
||||
"hasTag": hasTag,
|
||||
|
@ -147,8 +147,8 @@ func getBackendName(node *api.ServiceEntry, index int) string {
|
|||
|
||||
// TODO: Deprecated
|
||||
// Deprecated replaced by Stickiness
|
||||
func getSticky(tags []string) string {
|
||||
stickyTag := getTag(label.TraefikBackendLoadBalancerSticky, tags, "")
|
||||
func (p *CatalogProvider) getSticky(tags []string) string {
|
||||
stickyTag := p.getAttribute(label.SuffixBackendLoadBalancerSticky, tags, "")
|
||||
if len(stickyTag) > 0 {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
|
||||
} else {
|
||||
|
@ -157,11 +157,11 @@ func getSticky(tags []string) string {
|
|||
return stickyTag
|
||||
}
|
||||
|
||||
func hasStickinessLabel(tags []string) bool {
|
||||
stickinessTag := getTag(label.TraefikBackendLoadBalancerStickiness, tags, "")
|
||||
func (p *CatalogProvider) hasStickinessLabel(tags []string) bool {
|
||||
stickinessTag := p.getAttribute(label.SuffixBackendLoadBalancerStickiness, tags, "")
|
||||
return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
|
||||
}
|
||||
|
||||
func getStickinessCookieName(tags []string) string {
|
||||
return getTag(label.TraefikBackendLoadBalancerStickinessCookieName, tags, "")
|
||||
func (p *CatalogProvider) getStickinessCookieName(tags []string) string {
|
||||
return p.getAttribute(label.SuffixBackendLoadBalancerStickinessCookieName, tags, "")
|
||||
}
|
||||
|
|
|
@ -1006,6 +1006,10 @@ func TestGetBasicAuth(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestHasStickinessLabel(t *testing.T) {
|
||||
p := &CatalogProvider{
|
||||
Prefix: "traefik",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
tags []string
|
||||
|
@ -1037,7 +1041,7 @@ func TestHasStickinessLabel(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := hasStickinessLabel(test.tags)
|
||||
actual := p.hasStickinessLabel(test.tags)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
|
|||
"getRateLimitsExtractorFunc": getFuncStringLabel(label.TraefikFrontendRateLimitExtractorFunc, ""),
|
||||
"getRateLimits": getRateLimits,
|
||||
// Headers
|
||||
"hasHeaders": hasHeaders,
|
||||
"hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders),
|
||||
"getRequestHeaders": getFuncMapLabel(label.TraefikFrontendRequestHeaders),
|
||||
"hasResponseHeaders": hasFunc(label.TraefikFrontendResponseHeaders),
|
||||
|
|
|
@ -187,6 +187,15 @@ func getRateLimits(container dockerData) map[string]*types.Rate {
|
|||
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
|
||||
|
||||
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
|
||||
func getServiceBackend(container dockerData, serviceName string) string {
|
||||
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
|
||||
|
|
|
@ -402,12 +402,22 @@ func TestDockerGetServiceBackend(t *testing.T) {
|
|||
})),
|
||||
expected: "fake-another-backend-myservice",
|
||||
},
|
||||
{
|
||||
container: containerJSON(name("foo.bar")),
|
||||
expected: "foo-bar-foo-bar-myservice",
|
||||
},
|
||||
{
|
||||
container: containerJSON(labels(map[string]string{
|
||||
"traefik.myservice.frontend.backend": "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 {
|
||||
|
|
|
@ -126,7 +126,10 @@ func sendConfigToChannel(configurationChan chan<- types.ConfigMessage, configura
|
|||
}
|
||||
|
||||
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 {
|
||||
return nil, fmt.Errorf("error reading configuration file: %s", err)
|
||||
}
|
||||
|
@ -142,9 +145,8 @@ func loadFileConfigFromDirectory(directory string, configuration *types.Configur
|
|||
|
||||
if configuration == nil {
|
||||
configuration = &types.Configuration{
|
||||
Frontends: make(map[string]*types.Frontend),
|
||||
Backends: make(map[string]*types.Backend),
|
||||
TLSConfiguration: make([]*tls.Configuration, 0),
|
||||
Frontends: make(map[string]*types.Frontend),
|
||||
Backends: make(map[string]*types.Backend),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
return func(f *types.Frontend) {
|
||||
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)
|
||||
|
||||
headers := types.Headers{
|
||||
headers := &types.Headers{
|
||||
CustomRequestHeaders: label.GetMapValue(i.Annotations, annotationKubernetesCustomRequestHeaders),
|
||||
CustomResponseHeaders: label.GetMapValue(i.Annotations, annotationKubernetesCustomResponseHeaders),
|
||||
AllowedHosts: label.GetSliceStringValue(i.Annotations, annotationKubernetesAllowedHosts),
|
||||
|
|
|
@ -137,18 +137,21 @@ func TestLoadIngresses(t *testing.T) {
|
|||
),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/bar", "PathPrefix:/bar"),
|
||||
route("foo", "Host:foo")),
|
||||
),
|
||||
frontend("foo/namedthing",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/namedthing", "PathPrefix:/namedthing"),
|
||||
route("foo", "Host:foo")),
|
||||
),
|
||||
frontend("bar",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(route("bar", "Host:bar")),
|
||||
),
|
||||
|
@ -222,6 +225,7 @@ func TestRuleType(t *testing.T) {
|
|||
require.NoError(t, err, "error loading ingresses")
|
||||
|
||||
expected := buildFrontends(frontend("host/path",
|
||||
headers(),
|
||||
routes(
|
||||
route("/path", fmt.Sprintf("%s:/path", test.frontendRuleType)),
|
||||
route("host", "Host:host")),
|
||||
|
@ -267,6 +271,7 @@ func TestGetPassHostHeader(t *testing.T) {
|
|||
backends(backend("foo/bar", lbMethod("wrr"), servers())),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
headers(),
|
||||
routes(
|
||||
route("/bar", "PathPrefix:/bar"),
|
||||
route("foo", "Host:foo")),
|
||||
|
@ -310,6 +315,7 @@ func TestGetPassTLSCert(t *testing.T) {
|
|||
expected := buildConfiguration(
|
||||
backends(backend("foo/bar", lbMethod("wrr"), servers())),
|
||||
frontends(frontend("foo/bar",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
passTLSCert(),
|
||||
routes(
|
||||
|
@ -364,6 +370,7 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) {
|
|||
expected := buildConfiguration(
|
||||
backends(backend("foo", lbMethod("wrr"), servers())),
|
||||
frontends(frontend("foo",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(route("foo", "Host:foo")),
|
||||
)),
|
||||
|
@ -405,7 +412,9 @@ func TestHostlessIngress(t *testing.T) {
|
|||
|
||||
expected := buildConfiguration(
|
||||
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)
|
||||
|
@ -503,12 +512,14 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/bar", "PathPrefix:/bar"),
|
||||
route("foo", "Host:foo")),
|
||||
),
|
||||
frontend("bar",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(route("bar", "Host:bar"))),
|
||||
),
|
||||
|
@ -712,17 +723,20 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
headers(),
|
||||
routes(
|
||||
route("/bar", "PathPrefix:/bar"),
|
||||
route("foo", "Host:foo")),
|
||||
),
|
||||
frontend("other/stuff",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/stuff", "PathPrefix:/stuff"),
|
||||
route("other", "Host:other")),
|
||||
),
|
||||
frontend("other/",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
entryPoints("http", "https"),
|
||||
routes(
|
||||
|
@ -730,6 +744,7 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
route("other", "Host:other")),
|
||||
),
|
||||
frontend("other/sslstuff",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
passTLSCert(),
|
||||
routes(
|
||||
|
@ -737,6 +752,7 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
route("other", "Host:other")),
|
||||
),
|
||||
frontend("other/sslstuff",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
passTLSCert(),
|
||||
routes(
|
||||
|
@ -744,6 +760,7 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
route("other", "Host:other")),
|
||||
),
|
||||
frontend("basic/auth",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
basicAuth("myUser:myEncodedPW"),
|
||||
routes(
|
||||
|
@ -751,6 +768,7 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
route("basic", "Host:basic")),
|
||||
),
|
||||
frontend("redirect/https",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
redirectEntryPoint("https"),
|
||||
routes(
|
||||
|
@ -758,6 +776,7 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
route("redirect", "Host:redirect")),
|
||||
),
|
||||
frontend("test/whitelist-source-range",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
whitelistSourceRange("1.1.1.1/24", "1234:abcd::42/32"),
|
||||
routes(
|
||||
|
@ -765,6 +784,7 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
route("test", "Host:test")),
|
||||
),
|
||||
frontend("rewrite/api",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/api", "PathPrefix:/api;ReplacePath:/"),
|
||||
|
@ -824,6 +844,7 @@ func TestPriorityHeaderValue(t *testing.T) {
|
|||
),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
priority(1337),
|
||||
routes(
|
||||
|
@ -882,6 +903,7 @@ func TestInvalidPassTLSCertValue(t *testing.T) {
|
|||
),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/bar", "PathPrefix:/bar"),
|
||||
|
@ -939,6 +961,7 @@ func TestInvalidPassHostHeaderValue(t *testing.T) {
|
|||
),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/bar", "PathPrefix:/bar"),
|
||||
|
@ -1112,14 +1135,17 @@ func TestMissingResources(t *testing.T) {
|
|||
),
|
||||
frontends(
|
||||
frontend("fully_working",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(route("fully_working", "Host:fully_working")),
|
||||
),
|
||||
frontend("missing_endpoints",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(route("missing_endpoints", "Host:missing_endpoints")),
|
||||
),
|
||||
frontend("missing_endpoint_subsets",
|
||||
headers(),
|
||||
passHostHeader(),
|
||||
routes(route("missing_endpoint_subsets", "Host:missing_endpoint_subsets")),
|
||||
),
|
||||
|
|
|
@ -27,26 +27,27 @@ const (
|
|||
SuffixFrontendAuthBasic = "frontend.auth.basic"
|
||||
SuffixFrontendBackend = "frontend.backend"
|
||||
SuffixFrontendEntryPoints = "frontend.entryPoints"
|
||||
SuffixFrontendRequestHeaders = "frontend.headers.customRequestHeaders"
|
||||
SuffixFrontendResponseHeaders = "frontend.headers.customResponseHeaders"
|
||||
SuffixFrontendHeadersAllowedHosts = "frontend.headers.allowedHosts"
|
||||
SuffixFrontendHeadersHostsProxyHeaders = "frontend.headers.hostsProxyHeaders"
|
||||
SuffixFrontendHeadersSSLRedirect = "frontend.headers.SSLRedirect"
|
||||
SuffixFrontendHeadersSSLTemporaryRedirect = "frontend.headers.SSLTemporaryRedirect"
|
||||
SuffixFrontendHeadersSSLHost = "frontend.headers.SSLHost"
|
||||
SuffixFrontendHeadersSSLProxyHeaders = "frontend.headers.SSLProxyHeaders"
|
||||
SuffixFrontendHeadersSTSSeconds = "frontend.headers.STSSeconds"
|
||||
SuffixFrontendHeadersSTSIncludeSubdomains = "frontend.headers.STSIncludeSubdomains"
|
||||
SuffixFrontendHeadersSTSPreload = "frontend.headers.STSPreload"
|
||||
SuffixFrontendHeadersForceSTSHeader = "frontend.headers.forceSTSHeader"
|
||||
SuffixFrontendHeadersFrameDeny = "frontend.headers.frameDeny"
|
||||
SuffixFrontendHeadersCustomFrameOptionsValue = "frontend.headers.customFrameOptionsValue"
|
||||
SuffixFrontendHeadersContentTypeNosniff = "frontend.headers.contentTypeNosniff"
|
||||
SuffixFrontendHeadersBrowserXSSFilter = "frontend.headers.browserXSSFilter"
|
||||
SuffixFrontendHeadersContentSecurityPolicy = "frontend.headers.contentSecurityPolicy"
|
||||
SuffixFrontendHeadersPublicKey = "frontend.headers.publicKey"
|
||||
SuffixFrontendHeadersReferrerPolicy = "frontend.headers.referrerPolicy"
|
||||
SuffixFrontendHeadersIsDevelopment = "frontend.headers.isDevelopment"
|
||||
SuffixFrontendHeaders = "frontend.headers."
|
||||
SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders"
|
||||
SuffixFrontendResponseHeaders = SuffixFrontendHeaders + "customResponseHeaders"
|
||||
SuffixFrontendHeadersAllowedHosts = SuffixFrontendHeaders + "allowedHosts"
|
||||
SuffixFrontendHeadersHostsProxyHeaders = SuffixFrontendHeaders + "hostsProxyHeaders"
|
||||
SuffixFrontendHeadersSSLRedirect = SuffixFrontendHeaders + "SSLRedirect"
|
||||
SuffixFrontendHeadersSSLTemporaryRedirect = SuffixFrontendHeaders + "SSLTemporaryRedirect"
|
||||
SuffixFrontendHeadersSSLHost = SuffixFrontendHeaders + "SSLHost"
|
||||
SuffixFrontendHeadersSSLProxyHeaders = SuffixFrontendHeaders + "SSLProxyHeaders"
|
||||
SuffixFrontendHeadersSTSSeconds = SuffixFrontendHeaders + "STSSeconds"
|
||||
SuffixFrontendHeadersSTSIncludeSubdomains = SuffixFrontendHeaders + "STSIncludeSubdomains"
|
||||
SuffixFrontendHeadersSTSPreload = SuffixFrontendHeaders + "STSPreload"
|
||||
SuffixFrontendHeadersForceSTSHeader = SuffixFrontendHeaders + "forceSTSHeader"
|
||||
SuffixFrontendHeadersFrameDeny = SuffixFrontendHeaders + "frameDeny"
|
||||
SuffixFrontendHeadersCustomFrameOptionsValue = SuffixFrontendHeaders + "customFrameOptionsValue"
|
||||
SuffixFrontendHeadersContentTypeNosniff = SuffixFrontendHeaders + "contentTypeNosniff"
|
||||
SuffixFrontendHeadersBrowserXSSFilter = SuffixFrontendHeaders + "browserXSSFilter"
|
||||
SuffixFrontendHeadersContentSecurityPolicy = SuffixFrontendHeaders + "contentSecurityPolicy"
|
||||
SuffixFrontendHeadersPublicKey = SuffixFrontendHeaders + "publicKey"
|
||||
SuffixFrontendHeadersReferrerPolicy = SuffixFrontendHeaders + "referrerPolicy"
|
||||
SuffixFrontendHeadersIsDevelopment = SuffixFrontendHeaders + "isDevelopment"
|
||||
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
|
||||
SuffixFrontendPassTLSCert = "frontend.passTLSCert"
|
||||
SuffixFrontendPriority = "frontend.priority"
|
||||
|
@ -92,6 +93,7 @@ const (
|
|||
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
|
||||
TraefikFrontendValue = Prefix + SuffixFrontendValue
|
||||
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
|
||||
TraefikFrontendHeaders = Prefix + SuffixFrontendHeaders
|
||||
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
|
||||
TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders
|
||||
TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts
|
||||
|
|
|
@ -64,6 +64,7 @@ func (p *Provider) buildConfiguration() *types.Configuration {
|
|||
"getRateLimitsExtractorFunc": getFuncStringService(label.SuffixFrontendRateLimitExtractorFunc, ""),
|
||||
"getRateLimits": getRateLimits,
|
||||
// Headers
|
||||
"hasHeaders": hasPrefixFuncService(label.TraefikFrontendHeaders),
|
||||
"hasRequestHeaders": hasFuncService(label.SuffixFrontendRequestHeaders),
|
||||
"getRequestHeaders": getFuncMapService(label.SuffixFrontendRequestHeaders),
|
||||
"hasResponseHeaders": hasFuncService(label.SuffixFrontendResponseHeaders),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mkdocs==0.16.3
|
||||
mkdocs>=0.17.2
|
||||
pymdown-extensions>=1.4
|
||||
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 {
|
||||
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
|
||||
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)
|
||||
} 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)
|
||||
}
|
||||
|
@ -980,10 +980,9 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
|
|||
continue frontend
|
||||
}
|
||||
|
||||
var headerMiddleware *middlewares.HeaderStruct
|
||||
headerMiddleware := middlewares.NewHeaderFromStruct(frontend.Headers)
|
||||
var responseModifier func(res *http.Response) error
|
||||
if frontend.Headers.HasCustomHeadersDefined() {
|
||||
headerMiddleware = middlewares.NewHeaderFromStruct(frontend.Headers)
|
||||
if headerMiddleware != nil {
|
||||
responseModifier = headerMiddleware.ModifyResponseHeaders
|
||||
}
|
||||
|
||||
|
@ -1166,8 +1165,9 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
|
|||
log.Debugf("Adding header middleware for frontend %s", frontendName)
|
||||
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)
|
||||
n.UseFunc(secureMiddleware.HandlerFuncWithNext)
|
||||
}
|
||||
|
|
|
@ -179,6 +179,7 @@
|
|||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ if hasHeaders $container}}
|
||||
[frontends."frontend-{{$frontend}}".headers]
|
||||
{{if hasSSLRedirectHeaders $container}}
|
||||
SSLRedirect = {{getSSLRedirectHeaders $container}}
|
||||
|
@ -258,6 +259,7 @@
|
|||
{{$k}} = "{{$v}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
||||
rule = "{{getFrontendRule $container}}"
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
replacement = "{{$frontend.RedirectReplacement}}"
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Headers }}
|
||||
[frontends."{{$frontendName}}".headers]
|
||||
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
|
||||
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
|
||||
|
@ -85,9 +86,10 @@
|
|||
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
|
||||
{{$k}} = "{{$v}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{range $routeName, $route := $frontend.Routes}}
|
||||
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
||||
rule = "{{$route.Rule}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
|
@ -103,6 +103,7 @@
|
|||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if hasHeaders $app $serviceName }}
|
||||
[frontends."{{ getFrontendName $app $serviceName }}".headers]
|
||||
{{if hasSSLRedirectHeaders $app $serviceName}}
|
||||
SSLRedirect = {{getSSLRedirectHeaders $app $serviceName}}
|
||||
|
@ -182,6 +183,7 @@
|
|||
{{$k}} = "{{$v}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends."{{ getFrontendName $app $serviceName }}".routes."route-host{{$app.ID | replace "/" "-"}}{{getServiceNameSuffix $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
|
||||
func (h Headers) HasCustomHeadersDefined() bool {
|
||||
return len(h.CustomResponseHeaders) != 0 ||
|
||||
len(h.CustomRequestHeaders) != 0
|
||||
func (h *Headers) HasCustomHeadersDefined() bool {
|
||||
return h != nil && (len(h.CustomResponseHeaders) != 0 ||
|
||||
len(h.CustomRequestHeaders) != 0)
|
||||
}
|
||||
|
||||
// HasSecureHeadersDefined checks to see if any of the secure header elements have been set
|
||||
func (h Headers) HasSecureHeadersDefined() bool {
|
||||
return len(h.AllowedHosts) != 0 ||
|
||||
func (h *Headers) HasSecureHeadersDefined() bool {
|
||||
return h != nil && (len(h.AllowedHosts) != 0 ||
|
||||
len(h.HostsProxyHeaders) != 0 ||
|
||||
h.SSLRedirect ||
|
||||
h.SSLTemporaryRedirect ||
|
||||
|
@ -137,7 +137,7 @@ func (h Headers) HasSecureHeadersDefined() bool {
|
|||
h.ContentSecurityPolicy != "" ||
|
||||
h.PublicKey != "" ||
|
||||
h.ReferrerPolicy != "" ||
|
||||
h.IsDevelopment
|
||||
h.IsDevelopment)
|
||||
}
|
||||
|
||||
// Frontend holds frontend configuration.
|
||||
|
@ -150,7 +150,7 @@ type Frontend struct {
|
|||
Priority int `json:"priority"`
|
||||
BasicAuth []string `json:"basicAuth"`
|
||||
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"`
|
||||
Headers Headers `json:"headers,omitempty"`
|
||||
Headers *Headers `json:"headers,omitempty"`
|
||||
Errors map[string]*ErrorPage `json:"errors,omitempty"`
|
||||
RateLimit *RateLimit `json:"ratelimit,omitempty"`
|
||||
Redirect *Redirect `json:"redirect,omitempty"`
|
||||
|
|
Loading…
Reference in a new issue