Compare commits

...

20 Commits

Author SHA1 Message Date
SALLEYRON Julien
22bdbd2498 Prepare release 1.5.0-rc4 2018-01-04 15:22:03 +01:00
Ludovic Fernandez
287fb78654 Split Consul and Consul Catalog documentation 2018-01-04 14:48:03 +01:00
SALLEYRON Julien
5b24403c8e Don't panic if ResponseWriter does not implement CloseNotify 2018-01-04 11:18:03 +01:00
Julien Maitrehenry
e83599dd08 Add a note on how to add label to a docker compose file 2018-01-04 10:34:03 +01:00
SALLEYRON Julien
f30ad20c9b Use gorilla readMessage and writeMessage instead of just an io.Copy 2018-01-03 15:32:03 +01:00
Timo Reimann
01e17b6c3e k8s guide: Leave note about assumed DaemonSet usage. 2018-01-03 09:12:03 +01:00
SALLEYRON Julien
3e13ebec93 We need to flush the end of the body when retry is streamed 2018-01-02 16:02:03 +01:00
Fernandez Ludovic
23c1a9ca8e Merge branch 'v1.4' into v1.5 2018-01-02 13:10:11 +01:00
Michael
741c739ef1 Prepare release v1.4.6 2018-01-02 12:54:03 +01:00
SALLEYRON Julien
52f16e11a8 Use gorilla readMessage and writeMessage instead of just an io.Copy 2018-01-02 12:30:05 +01:00
Michael
0ee6973e2f Upgrade docs dependencies and adapt configuration 2018-01-02 11:28:02 +01:00
Timo Reimann
4819974a1c Improve Marathon service label documentation. 2018-01-02 11:08:02 +01:00
Michael
e8e8b41eed Normalize serviceName added to the service backend names 2018-01-02 10:52:03 +01:00
Krzysztof Pędrys
7d23d3c0a4 Typo in docker.endpoint TCP port. 2018-01-02 10:38:03 +01:00
Ludovic Fernandez
718fc7a79d Fix bug report command 2018-01-02 10:14:03 +01:00
Ludovic Fernandez
bfd142b13b Fix custom headers template 2018-01-02 10:10:04 +01:00
Ludovic Fernandez
75533b2beb Use prefix for sticky and stickiness tags. 2018-01-02 09:44:02 +01:00
NicoMen
9a7821b8fa Send empty configuration from file provider 2017-12-21 21:24:03 +01:00
lishaoxiong
e8333883df Add tests for TLS dynamic configuration in ETCD3 2017-12-21 18:02:04 +01:00
NicoMen
1e44e339ad Allow deleting dynamically all TLS certificates from an entryPoint 2017-12-21 14:16:03 +01:00
40 changed files with 867 additions and 395 deletions

View File

@@ -1,5 +1,36 @@
# Change Log
## [v1.5.0-rc4](https://github.com/containous/traefik/tree/v1.5.0-rc4) (2018-01-04)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc3...v1.5.0-rc4)
**Bug fixes:**
- **[consulcatalog]** Use prefix for sticky and stickiness tags. ([#2624](https://github.com/containous/traefik/pull/2624) by [ldez](https://github.com/ldez))
- **[file,tls]** Send empty configuration from file provider ([#2609](https://github.com/containous/traefik/pull/2609) by [nmengin](https://github.com/nmengin))
- **[middleware,docker,k8s]** Fix custom headers template ([#2621](https://github.com/containous/traefik/pull/2621) by [ldez](https://github.com/ldez))
- **[middleware]** Don't panic if ResponseWriter does not implement CloseNotify ([#2651](https://github.com/containous/traefik/pull/2651) by [Juliens](https://github.com/Juliens))
- **[middleware]** We need to flush the end of the body when retry is streamed ([#2644](https://github.com/containous/traefik/pull/2644) by [Juliens](https://github.com/Juliens))
- **[tls]** Allow deleting dynamically all TLS certificates from an entryPoint ([#2603](https://github.com/containous/traefik/pull/2603) by [nmengin](https://github.com/nmengin))
- **[websocket]** Use gorilla readMessage and writeMessage instead of just an io.Copy ([#2650](https://github.com/containous/traefik/pull/2650) by [Juliens](https://github.com/Juliens))
**Documentation:**
- **[consul,consulcatalog]** Split Consul and Consul Catalog documentation ([#2654](https://github.com/containous/traefik/pull/2654) by [ldez](https://github.com/ldez))
- **[docker/swarm]** Typo in docker.endpoint TCP port. ([#2626](https://github.com/containous/traefik/pull/2626) by [redhandpl](https://github.com/redhandpl))
- **[docker]** Add a note on how to add label to a docker compose file ([#2611](https://github.com/containous/traefik/pull/2611) by [jmaitrehenry](https://github.com/jmaitrehenry))
- **[k8s]** k8s guide: Leave note about assumed DaemonSet usage. ([#2634](https://github.com/containous/traefik/pull/2634) by [timoreimann](https://github.com/timoreimann))
- **[marathon]** Improve Marathon service label documentation. ([#2635](https://github.com/containous/traefik/pull/2635) by [timoreimann](https://github.com/timoreimann))
**Misc:**
- **[etcd,kv,tls]** Add tests for TLS dynamic configuration in ETCD3 ([#2606](https://github.com/containous/traefik/pull/2606) by [dahefanteng](https://github.com/dahefanteng))
- Merge v1.4.6 into v1.5 ([#2642](https://github.com/containous/traefik/pull/2642) by [ldez](https://github.com/ldez))
## [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)

View File

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

View File

@@ -219,6 +219,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
replacement = "{{getRedirectReplacement $container}}"
{{end}}
{{ if hasHeaders $container}}
[frontends."frontend-{{$frontend}}".headers]
{{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}}
@@ -265,6 +266,16 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
AllowedHosts = [{{range getAllowedHostsHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasHostsProxyHeaders $container}}
HostsProxyHeaders = [{{range getHostsProxyHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasRequestHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customrequestheaders]
{{range $k, $v := getRequestHeaders $container}}
@@ -277,24 +288,14 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.AllowedHosts]
{{range getAllowedHostsHeaders $container}}
"{{.}}"
{{end}}
{{end}}
{{if hasHostsProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.HostsProxyHeaders]
{{range getHostsProxyHeaders $container}}
"{{.}}"
{{end}}
{{end}}
{{if hasSSLProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.SSLProxyHeaders]
{{range $k, $v := getSSLProxyHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}"
{{end}}
@@ -445,6 +446,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}}
@@ -461,40 +463,45 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
PublicKey = "{{$frontend.Headers.PublicKey}}"
ReferrerPolicy = "{{$frontend.Headers.ReferrerPolicy}}"
IsDevelopment = {{$frontend.Headers.IsDevelopment}}
{{if $frontend.Headers.CustomRequestHeaders}}
[frontends."{{$frontendName}}".headers.customrequestheaders]
{{if $frontend.Headers.AllowedHosts}}
AllowedHosts = [{{range $frontend.Headers.AllowedHosts}}
"{{.}}",
{{end}}]
{{end}}
{{if $frontend.Headers.HostsProxyHeaders}}
HostsProxyHeaders = [{{range $frontend.Headers.HostsProxyHeaders}}
"{{.}}",
{{end}}]
{{end}}
{{if $frontend.Headers.CustomRequestHeaders}}
[frontends."{{$frontendName}}".headers.customrequestheaders]
{{range $k, $v := $frontend.Headers.CustomRequestHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $frontend.Headers.CustomResponseHeaders}}
[frontends."{{$frontendName}}".headers.customresponseheaders]
{{end}}
{{if $frontend.Headers.CustomResponseHeaders}}
[frontends."{{$frontendName}}".headers.customresponseheaders]
{{range $k, $v := $frontend.Headers.CustomResponseHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $frontend.Headers.AllowedHosts}}
[frontends."{{$frontendName}}".headers.AllowedHosts]
{{range $frontend.Headers.AllowedHosts}}
"{{.}}"
{{end}}
{{end}}
{{if $frontend.Headers.HostsProxyHeaders}}
[frontends."{{$frontendName}}".headers.HostsProxyHeaders]
{{range $frontend.Headers.HostsProxyHeaders}}
"{{.}}"
{{end}}
{{end}}
{{if $frontend.Headers.SSLProxyHeaders}}
[frontends."{{$frontendName}}".headers.SSLProxyHeaders]
{{end}}
{{if $frontend.Headers.SSLProxyHeaders}}
[frontends."{{$frontendName}}".headers.SSLProxyHeaders]
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
{{range $routeName, $route := $frontend.Routes}}
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
{{range $routeName, $route := $frontend.Routes}}
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
rule = "{{$route.Rule}}"
{{end}}
{{end}}
{{end}}
`)

View File

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

View File

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

View File

@@ -1,6 +1,4 @@
# Consul Backend
## Consul Key-Value backend
# Consul Key-Value backend
Træfik can be configured to use Consul as a backend configuration.
@@ -61,97 +59,3 @@ prefix = "traefik"
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
## Consul Catalog backend
Træfik can be configured to use service discovery catalog of Consul as a backend configuration.
```toml
################################################################
# Consul Catalog configuration backend
################################################################
# Enable Consul Catalog configuration backend.
[consulCatalog]
# Consul server endpoint.
#
# Required
# Default: "127.0.0.1:8500"
#
endpoint = "127.0.0.1:8500"
# Expose Consul catalog services by default in Traefik.
#
# Optional
# Default: true
#
exposedByDefault = false
# Default domain used.
#
# Optional
#
domain = "consul.localhost"
# Prefix for Consul catalog tags.
#
# Optional
# Default: "traefik"
#
prefix = "traefik"
# Default frontEnd Rule for Consul services.
#
# The format is a Go Template with:
# - ".ServiceName", ".Domain" and ".Attributes" available
# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available
# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value
#
# Optional
# Default: "Host:{{.ServiceName}}.{{.Domain}}"
#
#frontEndRule = "Host:{{.ServiceName}}.{{Domain}}"
```
This backend will create routes matching on hostname based on the service name used in Consul.
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
### Tags
Additional settings can be defined using Consul Catalog tags.
| Tag | Description |
|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.backend.weight=10` | Assign this weight to the container |
| `traefik.backend.circuitbreaker=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend, ex: `NetworkErrorRatio() > 0.` |
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{{.ServiceName}}.{{.Domain}}`). |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.backend.loadbalancer=drr` | override the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
### Examples
If you want that Træfik uses Consul tags correctly you need to defined them like that:
```json
traefik.enable=true
traefik.tags=api
traefik.tags=external
```
If the prefix defined in Træfik configuration is `bla`, tags need to be defined like that:
```json
bla.enable=true
bla.tags=api
bla.tags=external
```

View File

@@ -0,0 +1,93 @@
# Consul Catalog backend
Træfik can be configured to use service discovery catalog of Consul as a backend configuration.
```toml
################################################################
# Consul Catalog configuration backend
################################################################
# Enable Consul Catalog configuration backend.
[consulCatalog]
# Consul server endpoint.
#
# Required
# Default: "127.0.0.1:8500"
#
endpoint = "127.0.0.1:8500"
# Expose Consul catalog services by default in Traefik.
#
# Optional
# Default: true
#
exposedByDefault = false
# Default domain used.
#
# Optional
#
domain = "consul.localhost"
# Prefix for Consul catalog tags.
#
# Optional
# Default: "traefik"
#
prefix = "traefik"
# Default frontEnd Rule for Consul services.
#
# The format is a Go Template with:
# - ".ServiceName", ".Domain" and ".Attributes" available
# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available
# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value
#
# Optional
# Default: "Host:{{.ServiceName}}.{{.Domain}}"
#
#frontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"
```
This backend will create routes matching on hostname based on the service name used in Consul.
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
### Tags
Additional settings can be defined using Consul Catalog tags.
| Tag | Description |
|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.backend.weight=10` | Assign this weight to the container |
| `traefik.backend.circuitbreaker=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend, ex: `NetworkErrorRatio() > 0.` |
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{{.ServiceName}}.{{.Domain}}`). |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.backend.loadbalancer=drr` | override the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
### Examples
If you want that Træfik uses Consul tags correctly you need to defined them like that:
```json
traefik.enable=true
traefik.tags=api
traefik.tags=external
```
If the prefix defined in Træfik configuration is `bla`, tags need to be defined like that:
```json
bla.enable=true
bla.tags=api
bla.tags=external
```

View File

@@ -145,6 +145,20 @@ To enable constraints see [backend-specific constraints section](/configuration/
## Labels: overriding default behaviour
!!! note
If you use a compose file, labels should be defined in the `deploy` part of your service.
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)).
```yaml
version: "3"
services:
whoami:
deploy:
labels:
traefik.docker.network: traefik
```
### On Containers
Labels can be used on containers to override default behaviour.
@@ -224,11 +238,13 @@ Services labels can be used for overriding default behaviour
!!! note
if a label is defined both as a `container label` and a `service label` (for example `traefik.<service-name>.port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `<service-name>` property (`port` in the example).
If a label is defined both as a `container label` and a `service label` (for example `traefik.<service-name>.port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `<service-name>` property (`port` in the example).
It's possible to mix `container labels` and `service labels`, in this case `container labels` are used as default value for missing `service labels` but no frontends are going to be created with the `container labels`.
More details in this [example](/user-guide/docker-and-lets-encrypt/#labels).
!!! warning
when running inside a container, Træfik will need network access through:
When running inside a container, Træfik will need network access through:
`docker network connect <network> <traefik-container>`

View File

@@ -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 |
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -180,9 +183,9 @@ Labels can be used on containers to override default behaviour:
| `traefik.frontend.entryPoints=http,https` | assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
### 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 |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------|

View File

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

View File

@@ -257,7 +257,7 @@ curl $(minikube ip)
404 page not found
```
If you decided to use the deployment, then you need to target the correct NodePort, which can be seen then you execute `kubectl get services --namespace=kube-system`.
If you decided to use the deployment, then you need to target the correct NodePort, which can be seen when you execute `kubectl get services --namespace=kube-system`.
```sh
curl $(minikube ip):<NODEPORT>
@@ -269,6 +269,8 @@ curl $(minikube ip):<NODEPORT>
!!! note
We expect to see a 404 response here as we haven't yet given Træfik any configuration.
All further examples below assume a DaemonSet installation. Deployment users will need to append the NodePort when constructing requests.
## Deploy Træfik using Helm Chart
Instead of installing Træfik via an own object, you can also use the Træfik Helm chart.

View File

@@ -111,7 +111,7 @@ And there, the same global configuration in the Key-value Store (using `prefix =
| `/traefik/entrypoints/https/tls/certificates/0/keyfile` | `integration/fixtures/https/snitest.com.key` |
| `/traefik/entrypoints/https/tls/certificates/1/certfile` | `--BEGIN CERTIFICATE--<cert file content>--END CERTIFICATE--` |
| `/traefik/entrypoints/https/tls/certificates/1/keyfile` | `--BEGIN CERTIFICATE--<key file content>--END CERTIFICATE--` |
| `/traefik/entrypoints/other-https/address` | `:4443`
| `/traefik/entrypoints/other-https/address` | `:4443` |
| `/traefik/consul/endpoint` | `127.0.0.1:8500` |
| `/traefik/consul/watch` | `true` |
| `/traefik/consul/prefix` | `traefik` |
@@ -341,9 +341,10 @@ And there, the same dynamic configuration in a KV Store (using `prefix = "traefi
| Key | Value |
|----------------------------------------------------|-----------------------|
| `/traefik/tlsconfiguration/2/entrypoints` | `https,other-https` |
| `/traefik/tlsconfiguration/2/entrypoints` | `https,other-https` |
| `/traefik/tlsconfiguration/2/certificate/certfile` | `<cert file content>` |
| `/traefik/tlsconfiguration/2/certificate/certfile` | `<key file content>` |
### Atomic configuration changes
Træfik can watch the backends/frontends configuration changes and generate its configuration automatically.
@@ -378,9 +379,9 @@ Here, although the `/traefik_configurations/2/...` keys have been set, the old c
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically.
@@ -392,9 +393,9 @@ Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://1
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
!!! note
Træfik *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik/alias`.

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

View File

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

2
glide.lock generated
View File

@@ -520,7 +520,7 @@ imports:
- name: github.com/VividCortex/gohistogram
version: 51564d9861991fb0ad0f531c99ef602d0f9866e6
- name: github.com/vulcand/oxy
version: 7b6e758ab449705195df638765c4ca472248908a
version: 812cebb8c764f2a78cb806267648b8728b4599ad
repo: https://github.com/containous/oxy.git
vcs: git
subpackages:

View File

@@ -573,6 +573,113 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) {
req.Header.Set("Host", tr2.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, "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]
address = ":4443"
[entryPoints.https.tls]
[entryPoints.https02]
address = ":8443"
[entryPoints.https02.tls]
[web]
address = ":8080"

View File

@@ -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,30 +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"),
},
EntryPoints: []string{"https"},
},
// 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)
}
}

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.
// The condition returns an error if the request body does not contain one of the given
// strings.

View File

@@ -52,18 +52,18 @@ func NewErrorPagesHandler(errorPage types.ErrorPage, backendURL string) (*ErrorP
}
func (ep *ErrorPagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
recorder := newRetryResponseRecorder()
recorder.responseWriter = w
recorder := newRetryResponseRecorder(w)
next.ServeHTTP(recorder, req)
w.WriteHeader(recorder.Code)
w.WriteHeader(recorder.GetCode())
//check the recorder code against the configured http status code ranges
for _, block := range ep.HTTPCodeRanges {
if recorder.Code >= block[0] && recorder.Code <= block[1] {
log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.Code)
finalURL := strings.Replace(ep.BackendURL, "{status}", strconv.Itoa(recorder.Code), -1)
if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] {
log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.GetCode())
finalURL := strings.Replace(ep.BackendURL, "{status}", strconv.Itoa(recorder.GetCode()), -1)
if newReq, err := http.NewRequest(http.MethodGet, finalURL, nil); err != nil {
w.Write([]byte(http.StatusText(recorder.Code)))
w.Write([]byte(http.StatusText(recorder.GetCode())))
} else {
ep.errorPageForwarder.ServeHTTP(w, newReq)
}
@@ -73,5 +73,5 @@ func (ep *ErrorPagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request,
//did not catch a configured status code so proceed with the request
utils.CopyHeaders(w.Header(), recorder.Header())
w.Write(recorder.Body.Bytes())
w.Write(recorder.GetBody().Bytes())
}

View File

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

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

View File

@@ -14,7 +14,7 @@ import (
// Compile time validation responseRecorder implements http interfaces correctly.
var (
_ Stateful = &retryResponseRecorder{}
_ Stateful = &retryResponseRecorderWithCloseNotify{}
)
// Retry is a middleware that retries requests
@@ -48,21 +48,21 @@ func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
// when proxying the HTTP requests to the backends. This happens in the custom RecordingErrorHandler.
newCtx := context.WithValue(r.Context(), defaultNetErrCtxKey, &netErrorOccurred)
recorder := newRetryResponseRecorder()
recorder.responseWriter = rw
recorder := newRetryResponseRecorder(rw)
retry.next.ServeHTTP(recorder, r.WithContext(newCtx))
// It's a stream request and the body gets already sent to the client.
// Therefore we should not send the response a second time.
if recorder.streamingResponseStarted {
if recorder.IsStreamingResponseStarted() {
recorder.Flush()
break
}
if !netErrorOccurred || attempts >= retry.attempts {
utils.CopyHeaders(rw.Header(), recorder.Header())
rw.WriteHeader(recorder.Code)
rw.Write(recorder.Body.Bytes())
rw.WriteHeader(recorder.GetCode())
rw.Write(recorder.GetBody().Bytes())
break
}
attempts++
@@ -114,9 +114,31 @@ func (l RetryListeners) Retried(req *http.Request, attempt int) {
}
}
// retryResponseRecorder is an implementation of http.ResponseWriter that
type retryResponseRecorder interface {
http.ResponseWriter
http.Flusher
GetCode() int
GetBody() *bytes.Buffer
IsStreamingResponseStarted() bool
}
// newRetryResponseRecorder returns an initialized retryResponseRecorder.
func newRetryResponseRecorder(rw http.ResponseWriter) retryResponseRecorder {
recorder := &retryResponseRecorderWithoutCloseNotify{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: http.StatusOK,
responseWriter: rw,
}
if _, ok := rw.(http.CloseNotifier); ok {
return &retryResponseRecorderWithCloseNotify{recorder}
}
return recorder
}
// retryResponseRecorderWithoutCloseNotify is an implementation of http.ResponseWriter that
// records its mutations for later inspection.
type retryResponseRecorder struct {
type retryResponseRecorderWithoutCloseNotify struct {
Code int // the HTTP response code from WriteHeader
HeaderMap http.Header // the HTTP response headers
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
@@ -126,17 +148,19 @@ type retryResponseRecorder struct {
streamingResponseStarted bool
}
// newRetryResponseRecorder returns an initialized retryResponseRecorder.
func newRetryResponseRecorder() *retryResponseRecorder {
return &retryResponseRecorder{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: http.StatusOK,
}
type retryResponseRecorderWithCloseNotify struct {
*retryResponseRecorderWithoutCloseNotify
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone
// away.
func (rw *retryResponseRecorderWithCloseNotify) CloseNotify() <-chan bool {
return rw.responseWriter.(http.CloseNotifier).CloseNotify()
}
// Header returns the response headers.
func (rw *retryResponseRecorder) Header() http.Header {
func (rw *retryResponseRecorderWithoutCloseNotify) Header() http.Header {
m := rw.HeaderMap
if m == nil {
m = make(http.Header)
@@ -145,8 +169,20 @@ func (rw *retryResponseRecorder) Header() http.Header {
return m
}
func (rw *retryResponseRecorderWithoutCloseNotify) GetCode() int {
return rw.Code
}
func (rw *retryResponseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer {
return rw.Body
}
func (rw *retryResponseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool {
return rw.streamingResponseStarted
}
// Write always succeeds and writes to rw.Body, if not nil.
func (rw *retryResponseRecorder) Write(buf []byte) (int, error) {
func (rw *retryResponseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) {
if rw.err != nil {
return 0, rw.err
}
@@ -154,24 +190,17 @@ func (rw *retryResponseRecorder) Write(buf []byte) (int, error) {
}
// WriteHeader sets rw.Code.
func (rw *retryResponseRecorder) WriteHeader(code int) {
func (rw *retryResponseRecorderWithoutCloseNotify) WriteHeader(code int) {
rw.Code = code
}
// Hijack hijacks the connection
func (rw *retryResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
func (rw *retryResponseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return rw.responseWriter.(http.Hijacker).Hijack()
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone
// away.
func (rw *retryResponseRecorder) CloseNotify() <-chan bool {
return rw.responseWriter.(http.CloseNotifier).CloseNotify()
}
// Flush sends any buffered data to the client.
func (rw *retryResponseRecorder) Flush() {
func (rw *retryResponseRecorderWithoutCloseNotify) Flush() {
if !rw.streamingResponseStarted {
utils.CopyHeaders(rw.responseWriter.Header(), rw.Header())
rw.responseWriter.WriteHeader(rw.Code)

View File

@@ -7,6 +7,8 @@ import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRetry(t *testing.T) {
@@ -134,3 +136,69 @@ type countingRetryListener struct {
func (l *countingRetryListener) Retried(req *http.Request, attempt int) {
l.timesCalled++
}
func TestRetryWithFlush(t *testing.T) {
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(200)
rw.Write([]byte("FULL "))
rw.(http.Flusher).Flush()
rw.Write([]byte("DATA"))
})
retry := NewRetry(1, next, &countingRetryListener{})
responseRecorder := httptest.NewRecorder()
retry.ServeHTTP(responseRecorder, &http.Request{})
if responseRecorder.Body.String() != "FULL DATA" {
t.Errorf("Wrong body %q want %q", responseRecorder.Body.String(), "FULL DATA")
}
}
func TestNewRetryResponseRecorder(t *testing.T) {
testCases := []struct {
desc string
rw http.ResponseWriter
expected http.ResponseWriter
}{
{
desc: "Without Close Notify",
rw: httptest.NewRecorder(),
expected: &retryResponseRecorderWithoutCloseNotify{},
},
{
desc: "With Close Notify",
rw: &mockRWCloseNotify{},
expected: &retryResponseRecorderWithCloseNotify{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rec := newRetryResponseRecorder(test.rw)
assert.IsType(t, rec, test.expected)
})
}
}
type mockRWCloseNotify struct{}
func (m *mockRWCloseNotify) CloseNotify() <-chan bool {
panic("implement me")
}
func (m *mockRWCloseNotify) Header() http.Header {
panic("implement me")
}
func (m *mockRWCloseNotify) Write([]byte) (int, error) {
panic("implement me")
}
func (m *mockRWCloseNotify) WriteHeader(int) {
panic("implement me")
}

View File

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

View File

@@ -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 &copy; 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 &copy; 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:
@@ -74,6 +71,7 @@ pages:
- 'Backend: Web': 'configuration/backends/web.md'
- 'Backend: BoltDB': 'configuration/backends/boltdb.md'
- 'Backend: Consul': 'configuration/backends/consul.md'
- 'Backend: Consul Catalog': 'configuration/backends/consulcatalog.md'
- 'Backend: Docker': 'configuration/backends/docker.md'
- 'Backend: DynamoDB': 'configuration/backends/dynamodb.md'
- 'Backend: ECS': 'configuration/backends/ecs.md'

View File

@@ -430,7 +430,7 @@ func (p *CatalogProvider) getBasicAuth(tags []string) []string {
}
func (p *CatalogProvider) getSticky(tags []string) string {
stickyTag := p.getTag(types.LabelBackendLoadbalancerSticky, tags, "")
stickyTag := p.getAttribute(types.SuffixBackendLoadBalancerSticky, tags, "")
if len(stickyTag) > 0 {
log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
} else {
@@ -440,12 +440,12 @@ func (p *CatalogProvider) getSticky(tags []string) string {
}
func (p *CatalogProvider) hasStickinessLabel(tags []string) bool {
stickinessTag := p.getTag(types.LabelBackendLoadbalancerStickiness, tags, "")
stickinessTag := p.getAttribute(types.SuffixBackendLoadBalancerStickiness, tags, "")
return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
}
func (p *CatalogProvider) getStickinessCookieName(tags []string) string {
return p.getTag(types.LabelBackendLoadbalancerStickinessCookieName, tags, "")
return p.getAttribute(types.SuffixBackendLoadBalancerStickinessCookieName, tags, "")
}
func (p *CatalogProvider) getAttribute(name string, tags []string, defaultValue string) string {

View File

@@ -12,14 +12,6 @@ import (
)
func TestConsulCatalogGetFrontendRule(t *testing.T) {
provider := &CatalogProvider{
Domain: "localhost",
Prefix: "traefik",
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
provider.setupFrontEndTemplate()
testCases := []struct {
desc string
service serviceUpdate
@@ -71,6 +63,14 @@ func TestConsulCatalogGetFrontendRule(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
provider := &CatalogProvider{
Domain: "localhost",
Prefix: "traefik",
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
provider.setupFrontEndTemplate()
actual := provider.getFrontendRule(test.service)
assert.Equal(t, test.expected, actual)
})

View File

@@ -290,6 +290,7 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
"getServiceBackend": getServiceBackend,
"getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange),
"hasHeaders": hasHeaders,
"hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeaders),
"getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeaders),
"hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeaders),
@@ -417,9 +418,9 @@ func getServiceNames(container dockerData) []string {
// Extract backend from labels for a given service and a given docker container
func getServiceBackend(container dockerData, serviceName string) string {
if value, ok := getContainerServiceLabel(container, serviceName, types.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 rule from labels for a given service and a given docker container
@@ -895,3 +896,15 @@ func hasRedirect(container dockerData) bool {
return hasLabel(types.LabelFrontendRedirectEntryPoint)(container) ||
hasLabel(types.LabelFrontendRedirectReplacement)(container) && hasLabel(types.LabelFrontendRedirectRegex)(container)
}
// TODO will be rewrite when merge on master
func hasHeaders(container dockerData) bool {
// LabelPrefix + "frontend.headers.
for key := range container.Labels {
if strings.HasPrefix(key, types.LabelPrefix+"frontend.headers.") {
return true
}
}
return false
}

View File

@@ -94,12 +94,22 @@ func TestDockerGetServiceBackend(t *testing.T) {
container: containerJSON(name("foo")),
expected: "foo-foo-myservice",
},
{
container: containerJSON(name("foo.bar")),
expected: "foo-bar-foo-bar-myservice",
},
{
container: containerJSON(labels(map[string]string{
types.LabelBackend: "another-backend",
})),
expected: "fake-another-backend-myservice",
},
{
container: containerJSON(labels(map[string]string{
types.LabelBackend: "another.backend",
})),
expected: "fake-another-backend-myservice",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.backend": "custom-backend",

View File

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

View File

@@ -208,7 +208,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
priority := getPriority(i)
headers := types.Headers{
headers := &types.Headers{
CustomRequestHeaders: getMapAnnotation(i, annotationKubernetesCustomRequestHeaders),
CustomResponseHeaders: getMapAnnotation(i, annotationKubernetesCustomResponseHeaders),
AllowedHosts: getSliceAnnotation(i, annotationKubernetesAllowedHosts),
@@ -364,7 +364,7 @@ func (p *Provider) loadConfig(templateObjects types.Configuration) *types.Config
}
func getSTSSeconds(i *v1beta1.Ingress) int64 {
value, err := strconv.ParseInt(i.ObjectMeta.Annotations[annotationKubernetesHSTSMaxAge], 10, 64)
value, err := strconv.ParseInt(i.Annotations[annotationKubernetesHSTSMaxAge], 10, 64)
if err == nil && value > 0 {
return value
}

View File

@@ -280,6 +280,7 @@ func TestLoadIngresses(t *testing.T) {
"foo/bar": {
Backend: "foo/bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -292,6 +293,7 @@ func TestLoadIngresses(t *testing.T) {
"foo/namedthing": {
Backend: "foo/namedthing",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/namedthing": {
Rule: "PathPrefix:/namedthing",
@@ -304,6 +306,7 @@ func TestLoadIngresses(t *testing.T) {
"bar": {
Backend: "bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"bar": {
Rule: "Host:bar",
@@ -409,6 +412,7 @@ func TestRuleType(t *testing.T) {
expected := map[string]*types.Frontend{
"host/path": {
Backend: "host/path",
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/path": {
Rule: fmt.Sprintf("%s:/path", test.frontendRuleType),
@@ -494,6 +498,7 @@ func TestGetPassHostHeader(t *testing.T) {
Frontends: map[string]*types.Frontend{
"foo/bar": {
Backend: "foo/bar",
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -580,6 +585,7 @@ func TestGetPassTLSCert(t *testing.T) {
Backend: "foo/bar",
PassTLSCert: true,
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -682,6 +688,7 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) {
"foo": {
Backend: "foo",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"foo": {
Rule: "Host:foo",
@@ -762,6 +769,7 @@ func TestHostlessIngress(t *testing.T) {
Frontends: map[string]*types.Frontend{
"/bar": {
Backend: "/bar",
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -980,6 +988,7 @@ func TestServiceAnnotations(t *testing.T) {
"foo/bar": {
Backend: "foo/bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -992,6 +1001,7 @@ func TestServiceAnnotations(t *testing.T) {
"bar": {
Backend: "bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"bar": {
Rule: "Host:bar",
@@ -1445,6 +1455,7 @@ func TestIngressAnnotations(t *testing.T) {
"foo/bar": {
Backend: "foo/bar",
PassHostHeader: false,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -1457,6 +1468,7 @@ func TestIngressAnnotations(t *testing.T) {
"other/stuff": {
Backend: "other/stuff",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/stuff": {
Rule: "PathPrefix:/stuff",
@@ -1469,6 +1481,7 @@ func TestIngressAnnotations(t *testing.T) {
"other/": {
Backend: "other/",
PassHostHeader: true,
Headers: &types.Headers{},
EntryPoints: []string{"http", "https"},
Routes: map[string]types.Route{
"/": {
@@ -1483,6 +1496,7 @@ func TestIngressAnnotations(t *testing.T) {
Backend: "other/sslstuff",
PassHostHeader: true,
PassTLSCert: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/sslstuff": {
Rule: "PathPrefix:/sslstuff",
@@ -1495,6 +1509,7 @@ func TestIngressAnnotations(t *testing.T) {
"basic/auth": {
Backend: "basic/auth",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/auth": {
Rule: "PathPrefix:/auth",
@@ -1508,6 +1523,7 @@ func TestIngressAnnotations(t *testing.T) {
"redirect/https": {
Backend: "redirect/https",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/https": {
Rule: "PathPrefix:/https",
@@ -1524,6 +1540,7 @@ func TestIngressAnnotations(t *testing.T) {
"test/whitelist-source-range": {
Backend: "test/whitelist-source-range",
PassHostHeader: true,
Headers: &types.Headers{},
WhitelistSourceRange: []string{
"1.1.1.1/24",
"1234:abcd::42/32",
@@ -1540,6 +1557,7 @@ func TestIngressAnnotations(t *testing.T) {
"rewrite/api": {
Backend: "rewrite/api",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/api": {
Rule: "PathPrefix:/api;ReplacePath:/",
@@ -1641,6 +1659,7 @@ func TestPriorityHeaderValue(t *testing.T) {
Backend: "foo/bar",
PassHostHeader: true,
Priority: 1337,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -1742,6 +1761,7 @@ func TestInvalidPassTLSCertValue(t *testing.T) {
Backend: "foo/bar",
PassTLSCert: false,
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -1842,6 +1862,7 @@ func TestInvalidPassHostHeaderValue(t *testing.T) {
"foo/bar": {
Backend: "foo/bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -2143,6 +2164,7 @@ func TestMissingResources(t *testing.T) {
"fully_working": {
Backend: "fully_working",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"fully_working": {
Rule: "Host:fully_working",
@@ -2152,6 +2174,7 @@ func TestMissingResources(t *testing.T) {
"missing_endpoints": {
Backend: "missing_endpoints",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"missing_endpoints": {
Rule: "Host:missing_endpoints",
@@ -2161,6 +2184,7 @@ func TestMissingResources(t *testing.T) {
"missing_endpoint_subsets": {
Backend: "missing_endpoint_subsets",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"missing_endpoint_subsets": {
Rule: "Host:missing_endpoint_subsets",

View File

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

View File

@@ -439,7 +439,11 @@ 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 != 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())
}
log.Infof("Server configuration reloaded on %s", s.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
@@ -976,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
}
@@ -1162,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)
}

View File

@@ -94,6 +94,7 @@
replacement = "{{getRedirectReplacement $container}}"
{{end}}
{{ if hasHeaders $container}}
[frontends."frontend-{{$frontend}}".headers]
{{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}}
@@ -140,6 +141,16 @@
{{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
AllowedHosts = [{{range getAllowedHostsHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasHostsProxyHeaders $container}}
HostsProxyHeaders = [{{range getHostsProxyHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasRequestHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customrequestheaders]
{{range $k, $v := getRequestHeaders $container}}
@@ -152,24 +163,14 @@
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.AllowedHosts]
{{range getAllowedHostsHeaders $container}}
"{{.}}"
{{end}}
{{end}}
{{if hasHostsProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.HostsProxyHeaders]
{{range getHostsProxyHeaders $container}}
"{{.}}"
{{end}}
{{end}}
{{if hasSSLProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.SSLProxyHeaders]
{{range $k, $v := getSSLProxyHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}"
{{end}}

View File

@@ -42,6 +42,7 @@
replacement = "{{$frontend.RedirectReplacement}}"
{{end}}
{{ if $frontend.Headers }}
[frontends."{{$frontendName}}".headers]
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
@@ -58,38 +59,43 @@
PublicKey = "{{$frontend.Headers.PublicKey}}"
ReferrerPolicy = "{{$frontend.Headers.ReferrerPolicy}}"
IsDevelopment = {{$frontend.Headers.IsDevelopment}}
{{if $frontend.Headers.CustomRequestHeaders}}
[frontends."{{$frontendName}}".headers.customrequestheaders]
{{if $frontend.Headers.AllowedHosts}}
AllowedHosts = [{{range $frontend.Headers.AllowedHosts}}
"{{.}}",
{{end}}]
{{end}}
{{if $frontend.Headers.HostsProxyHeaders}}
HostsProxyHeaders = [{{range $frontend.Headers.HostsProxyHeaders}}
"{{.}}",
{{end}}]
{{end}}
{{if $frontend.Headers.CustomRequestHeaders}}
[frontends."{{$frontendName}}".headers.customrequestheaders]
{{range $k, $v := $frontend.Headers.CustomRequestHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $frontend.Headers.CustomResponseHeaders}}
[frontends."{{$frontendName}}".headers.customresponseheaders]
{{end}}
{{if $frontend.Headers.CustomResponseHeaders}}
[frontends."{{$frontendName}}".headers.customresponseheaders]
{{range $k, $v := $frontend.Headers.CustomResponseHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $frontend.Headers.AllowedHosts}}
[frontends."{{$frontendName}}".headers.AllowedHosts]
{{range $frontend.Headers.AllowedHosts}}
"{{.}}"
{{end}}
{{end}}
{{if $frontend.Headers.HostsProxyHeaders}}
[frontends."{{$frontendName}}".headers.HostsProxyHeaders]
{{range $frontend.Headers.HostsProxyHeaders}}
"{{.}}"
{{end}}
{{end}}
{{if $frontend.Headers.SSLProxyHeaders}}
[frontends."{{$frontendName}}".headers.SSLProxyHeaders]
{{end}}
{{if $frontend.Headers.SSLProxyHeaders}}
[frontends."{{$frontendName}}".headers.SSLProxyHeaders]
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
{{range $routeName, $route := $frontend.Routes}}
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
{{range $routeName, $route := $frontend.Routes}}
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
rule = "{{$route.Rule}}"
{{end}}
{{end}}
{{end}}

View File

@@ -4,70 +4,73 @@ import "strings"
// Traefik labels
const (
LabelPrefix = "traefik."
SuffixPort = "port"
SuffixProtocol = "protocol"
SuffixWeight = "weight"
SuffixFrontendAuthBasic = "frontend.auth.basic"
SuffixFrontendBackend = "frontend.backend"
SuffixFrontendEntryPoints = "frontend.entryPoints"
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRule = "frontend.rule"
LabelDomain = LabelPrefix + "domain"
LabelEnable = LabelPrefix + "enable"
LabelPort = LabelPrefix + SuffixPort
LabelPortIndex = LabelPrefix + "portIndex"
LabelProtocol = LabelPrefix + SuffixProtocol
LabelTags = LabelPrefix + "tags"
LabelWeight = LabelPrefix + SuffixWeight
LabelFrontendAuthBasic = LabelPrefix + SuffixFrontendAuthBasic
LabelFrontendEntryPoints = LabelPrefix + SuffixFrontendEntryPoints
LabelFrontendPassHostHeader = LabelPrefix + SuffixFrontendPassHostHeader
LabelFrontendPassTLSCert = LabelPrefix + "frontend.passTLSCert"
LabelFrontendPriority = LabelPrefix + SuffixFrontendPriority
LabelFrontendRule = LabelPrefix + SuffixFrontendRule
LabelFrontendRuleType = LabelPrefix + "frontend.rule.type"
LabelFrontendRedirectEntryPoint = LabelPrefix + SuffixFrontendRedirectEntryPoint
LabelFrontendRedirectRegex = LabelPrefix + SuffixFrontendRedirectRegex
LabelFrontendRedirectReplacement = LabelPrefix + SuffixFrontendRedirectReplacement
LabelTraefikFrontendValue = LabelPrefix + "frontend.value"
LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange"
LabelFrontendRequestHeaders = LabelPrefix + "frontend.headers.customRequestHeaders"
LabelFrontendResponseHeaders = LabelPrefix + "frontend.headers.customResponseHeaders"
LabelFrontendAllowedHosts = LabelPrefix + "frontend.headers.allowedHosts"
LabelFrontendHostsProxyHeaders = LabelPrefix + "frontend.headers.hostsProxyHeaders"
LabelFrontendSSLRedirect = LabelPrefix + "frontend.headers.SSLRedirect"
LabelFrontendSSLTemporaryRedirect = LabelPrefix + "frontend.headers.SSLTemporaryRedirect"
LabelFrontendSSLHost = LabelPrefix + "frontend.headers.SSLHost"
LabelFrontendSSLProxyHeaders = LabelPrefix + "frontend.headers.SSLProxyHeaders"
LabelFrontendSTSSeconds = LabelPrefix + "frontend.headers.STSSeconds"
LabelFrontendSTSIncludeSubdomains = LabelPrefix + "frontend.headers.STSIncludeSubdomains"
LabelFrontendSTSPreload = LabelPrefix + "frontend.headers.STSPreload"
LabelFrontendForceSTSHeader = LabelPrefix + "frontend.headers.forceSTSHeader"
LabelFrontendFrameDeny = LabelPrefix + "frontend.headers.frameDeny"
LabelFrontendCustomFrameOptionsValue = LabelPrefix + "frontend.headers.customFrameOptionsValue"
LabelFrontendContentTypeNosniff = LabelPrefix + "frontend.headers.contentTypeNosniff"
LabelFrontendBrowserXSSFilter = LabelPrefix + "frontend.headers.browserXSSFilter"
LabelFrontendContentSecurityPolicy = LabelPrefix + "frontend.headers.contentSecurityPolicy"
LabelFrontendPublicKey = LabelPrefix + "frontend.headers.publicKey"
LabelFrontendReferrerPolicy = LabelPrefix + "frontend.headers.referrerPolicy"
LabelFrontendIsDevelopment = LabelPrefix + "frontend.headers.isDevelopment"
LabelBackend = LabelPrefix + "backend"
LabelBackendID = LabelPrefix + "backend.id"
LabelTraefikBackendCircuitbreaker = LabelPrefix + "backend.circuitbreaker"
LabelBackendCircuitbreakerExpression = LabelPrefix + "backend.circuitbreaker.expression"
LabelBackendHealthcheckPath = LabelPrefix + "backend.healthcheck.path"
LabelBackendHealthcheckInterval = LabelPrefix + "backend.healthcheck.interval"
LabelBackendLoadbalancerMethod = LabelPrefix + "backend.loadbalancer.method"
LabelBackendLoadbalancerSticky = LabelPrefix + "backend.loadbalancer.sticky"
LabelBackendLoadbalancerStickiness = LabelPrefix + "backend.loadbalancer.stickiness"
LabelBackendLoadbalancerStickinessCookieName = LabelPrefix + "backend.loadbalancer.stickiness.cookieName"
LabelBackendMaxconnAmount = LabelPrefix + "backend.maxconn.amount"
LabelBackendMaxconnExtractorfunc = LabelPrefix + "backend.maxconn.extractorfunc"
LabelPrefix = "traefik."
SuffixPort = "port"
SuffixProtocol = "protocol"
SuffixWeight = "weight"
SuffixFrontendAuthBasic = "frontend.auth.basic"
SuffixFrontendBackend = "frontend.backend"
SuffixFrontendEntryPoints = "frontend.entryPoints"
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRule = "frontend.rule"
SuffixBackendLoadBalancerSticky = "backend.loadbalancer.sticky"
SuffixBackendLoadBalancerStickiness = "backend.loadbalancer.stickiness"
SuffixBackendLoadBalancerStickinessCookieName = "backend.loadbalancer.stickiness.cookieName"
LabelDomain = LabelPrefix + "domain"
LabelEnable = LabelPrefix + "enable"
LabelPort = LabelPrefix + SuffixPort
LabelPortIndex = LabelPrefix + "portIndex"
LabelProtocol = LabelPrefix + SuffixProtocol
LabelTags = LabelPrefix + "tags"
LabelWeight = LabelPrefix + SuffixWeight
LabelFrontendAuthBasic = LabelPrefix + SuffixFrontendAuthBasic
LabelFrontendEntryPoints = LabelPrefix + SuffixFrontendEntryPoints
LabelFrontendPassHostHeader = LabelPrefix + SuffixFrontendPassHostHeader
LabelFrontendPassTLSCert = LabelPrefix + "frontend.passTLSCert"
LabelFrontendPriority = LabelPrefix + SuffixFrontendPriority
LabelFrontendRule = LabelPrefix + SuffixFrontendRule
LabelFrontendRuleType = LabelPrefix + "frontend.rule.type"
LabelFrontendRedirectEntryPoint = LabelPrefix + SuffixFrontendRedirectEntryPoint
LabelFrontendRedirectRegex = LabelPrefix + SuffixFrontendRedirectRegex
LabelFrontendRedirectReplacement = LabelPrefix + SuffixFrontendRedirectReplacement
LabelTraefikFrontendValue = LabelPrefix + "frontend.value"
LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange"
LabelFrontendRequestHeaders = LabelPrefix + "frontend.headers.customRequestHeaders"
LabelFrontendResponseHeaders = LabelPrefix + "frontend.headers.customResponseHeaders"
LabelFrontendAllowedHosts = LabelPrefix + "frontend.headers.allowedHosts"
LabelFrontendHostsProxyHeaders = LabelPrefix + "frontend.headers.hostsProxyHeaders"
LabelFrontendSSLRedirect = LabelPrefix + "frontend.headers.SSLRedirect"
LabelFrontendSSLTemporaryRedirect = LabelPrefix + "frontend.headers.SSLTemporaryRedirect"
LabelFrontendSSLHost = LabelPrefix + "frontend.headers.SSLHost"
LabelFrontendSSLProxyHeaders = LabelPrefix + "frontend.headers.SSLProxyHeaders"
LabelFrontendSTSSeconds = LabelPrefix + "frontend.headers.STSSeconds"
LabelFrontendSTSIncludeSubdomains = LabelPrefix + "frontend.headers.STSIncludeSubdomains"
LabelFrontendSTSPreload = LabelPrefix + "frontend.headers.STSPreload"
LabelFrontendForceSTSHeader = LabelPrefix + "frontend.headers.forceSTSHeader"
LabelFrontendFrameDeny = LabelPrefix + "frontend.headers.frameDeny"
LabelFrontendCustomFrameOptionsValue = LabelPrefix + "frontend.headers.customFrameOptionsValue"
LabelFrontendContentTypeNosniff = LabelPrefix + "frontend.headers.contentTypeNosniff"
LabelFrontendBrowserXSSFilter = LabelPrefix + "frontend.headers.browserXSSFilter"
LabelFrontendContentSecurityPolicy = LabelPrefix + "frontend.headers.contentSecurityPolicy"
LabelFrontendPublicKey = LabelPrefix + "frontend.headers.publicKey"
LabelFrontendReferrerPolicy = LabelPrefix + "frontend.headers.referrerPolicy"
LabelFrontendIsDevelopment = LabelPrefix + "frontend.headers.isDevelopment"
LabelBackend = LabelPrefix + "backend"
LabelBackendID = LabelPrefix + "backend.id"
LabelTraefikBackendCircuitbreaker = LabelPrefix + "backend.circuitbreaker"
LabelBackendCircuitbreakerExpression = LabelPrefix + "backend.circuitbreaker.expression"
LabelBackendHealthcheckPath = LabelPrefix + "backend.healthcheck.path"
LabelBackendHealthcheckInterval = LabelPrefix + "backend.healthcheck.interval"
LabelBackendLoadbalancerMethod = LabelPrefix + "backend.loadbalancer.method"
LabelBackendLoadbalancerSticky = LabelPrefix + SuffixBackendLoadBalancerSticky
LabelBackendLoadbalancerStickiness = LabelPrefix + SuffixBackendLoadBalancerStickiness
LabelBackendLoadbalancerStickinessCookieName = LabelPrefix + SuffixBackendLoadBalancerStickinessCookieName
LabelBackendMaxconnAmount = LabelPrefix + "backend.maxconn.amount"
LabelBackendMaxconnExtractorfunc = LabelPrefix + "backend.maxconn.extractorfunc"
)
//ServiceLabel converts a key value of Label*, given a serviceName, into a pattern <LabelPrefix>.<serviceName>.<property>

View File

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

View File

@@ -5,7 +5,6 @@ package forward
import (
"crypto/tls"
"io"
"net/http"
"net/http/httptest"
"net/http/httputil"
@@ -214,7 +213,7 @@ func (f *Forwarder) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if f.log.Level >= log.DebugLevel {
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
logEntry.Debug("vulcand/oxy/forward: begin ServeHttp on request")
defer logEntry.Debug("vulcand/oxy/forward: competed ServeHttp on request")
defer logEntry.Debug("vulcand/oxy/forward: completed ServeHttp on request")
}
if f.stateListener != nil {
@@ -333,27 +332,27 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request,
defer targetConn.Close()
errc := make(chan error, 2)
replicate := func(dst io.Writer, src io.Reader, dstName string, srcName string) {
_, errCopy := io.Copy(dst, src)
if errCopy != nil {
f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using io.Copy: %v", srcName, dstName, errCopy)
} else {
f.log.Infof("vulcand/oxy/forward/websocket: Copying from %s to %s using io.Copy completed without error.", srcName, dstName)
replicateWebsocketConn := func(dst, src *websocket.Conn, dstName, srcName string) {
var err error
for {
msgType, msg, err := src.ReadMessage()
if err != nil {
f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using ReadMessage: %v", srcName, dstName, err)
break
}
err = dst.WriteMessage(msgType, msg)
if err != nil {
f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using WriteMessage: %v", srcName, dstName, err)
break
}
}
errc <- errCopy
errc <- err
}
go replicate(targetConn.UnderlyingConn(), underlyingConn.UnderlyingConn(), "backend", "client")
go replicateWebsocketConn(underlyingConn, targetConn, "client", "backend")
go replicateWebsocketConn(targetConn, underlyingConn, "backend", "client")
// Try to read the first message
msgType, msg, err := targetConn.ReadMessage()
if err != nil {
log.Errorf("vulcand/oxy/forward/websocket: Couldn't read first message : %v", err)
} else {
underlyingConn.WriteMessage(msgType, msg)
}
go replicate(underlyingConn.UnderlyingConn(), targetConn.UnderlyingConn(), "client", "backend")
<-errc
}