Merge branch 'v1.6' into master

This commit is contained in:
Fernandez Ludovic 2018-04-25 08:22:17 +02:00
commit 3b3ca89483
55 changed files with 844 additions and 569 deletions

View file

@ -220,7 +220,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
SamplingServerURL: "http://localhost:5778/sampling",
SamplingType: "const",
SamplingParam: 1.0,
LocalAgentHostPort: "127.0.0.1:6832",
LocalAgentHostPort: "127.0.0.1:6831",
},
Zipkin: &zipkin.Config{
HTTPEndpoint: "http://localhost:9411/api/v1/spans",

View file

@ -327,7 +327,7 @@ func (gc *GlobalConfiguration) initTracing() {
SamplingServerURL: "http://localhost:5778/sampling",
SamplingType: "const",
SamplingParam: 1.0,
LocalAgentHostPort: "127.0.0.1:6832",
LocalAgentHostPort: "127.0.0.1:6831",
}
}
if gc.Tracing.Zipkin != nil {

View file

@ -138,7 +138,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
SamplingServerURL: "http://localhost:5778/sampling",
SamplingType: "const",
SamplingParam: 1.0,
LocalAgentHostPort: "127.0.0.1:6832",
LocalAgentHostPort: "127.0.0.1:6831",
},
Zipkin: nil,
},
@ -151,7 +151,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
SamplingServerURL: "http://localhost:5778/sampling",
SamplingType: "const",
SamplingParam: 1.0,
LocalAgentHostPort: "127.0.0.1:6832",
LocalAgentHostPort: "127.0.0.1:6831",
},
},
expected: &tracing.Tracing{
@ -173,7 +173,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
SamplingServerURL: "http://localhost:5778/sampling",
SamplingType: "const",
SamplingParam: 1.0,
LocalAgentHostPort: "127.0.0.1:6832",
LocalAgentHostPort: "127.0.0.1:6831",
},
Zipkin: &zipkin.Config{
HTTPEndpoint: "http://powpow:9411/api/v1/spans",

View file

@ -262,7 +262,7 @@ This allows for setting headers such as `X-Script-Name` to be added to the reque
!!! warning
If the custom header name is the same as one header name of the request or response, it will be replaced.
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, and the `X-Custom-Response-Header` added to the response.
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request and the `X-Custom-Response-Header` header added to the response.
```toml
[frontends]
@ -276,7 +276,7 @@ In this example, all matches to the path `/cheese` will have the `X-Script-Name`
rule = "PathPrefixStrip:/cheese"
```
In this second example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` removed to the request and the `X-Custom-Response-Header` removed to the response.
In this second example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` header removed from the request, and the `X-Custom-Response-Header` header removed from the response.
```toml
[frontends]
@ -323,12 +323,49 @@ In this example, traffic routed through the first frontend will have the `X-Fram
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
#### Servers
Servers are simply defined using a `url`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
!!! note
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
Here is an example of backends and servers definition:
```toml
[backends]
[backends.backend1]
# ...
[backends.backend1.servers.server1]
url = "http://172.17.0.2:80"
weight = 10
[backends.backend1.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
[backends.backend2]
# ...
[backends.backend2.servers.server1]
url = "http://172.17.0.4:80"
weight = 1
[backends.backend2.servers.server2]
url = "http://172.17.0.5:80"
weight = 2
```
- Two backends are defined: `backend1` and `backend2`
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1`.
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2`.
#### Load-balancing
Various methods of load-balancing are supported:
- `wrr`: Weighted Round Robin.
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others.
It also rolls back to original weights if the servers have changed.
#### Circuit breakers
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
Initial state is Standby. CB observes the statistics and does not modify the request.
In case the condition matches, CB enters Tripped state, where it responds with predefined code or redirects to another frontend.
@ -346,6 +383,26 @@ For example:
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in ranges [500-600) and [0-600).
Here is an example of backends and servers definition:
```toml
[backends]
[backends.backend1]
[backends.backend1.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[backends.backend1.servers.server1]
url = "http://172.17.0.2:80"
weight = 10
[backends.backend1.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
```
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
#### Maximum connections
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can also be applied to each backend.
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and `maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to evaluate the maximum connections.
@ -357,13 +414,14 @@ For example:
[backends.backend1.maxconn]
amount = 10
extractorfunc = "request.host"
# ...
```
- `backend1` will return `HTTP code 429 Too Many Requests` if there are already 10 requests in progress for the same Host header.
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
### Sticky sessions
#### Sticky sessions
Sticky sessions are supported with both load balancers.
When sticky sessions are enabled, a cookie is set on the initial request.
@ -371,7 +429,6 @@ The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`).
On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy.
If not, a new backend will be assigned.
```toml
[backends]
[backends.backend1]
@ -395,10 +452,10 @@ The deprecated way:
sticky = true
```
### Health Check
#### Health Check
A health check can be configured in order to remove a backend from LB rotation as long as it keeps returning HTTP status codes other than `200 OK` to HTTP GET requests periodically carried out by Traefik.
The check is defined by a pathappended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how often the health check should be executed (the default being 30 seconds).
The check is defined by a path appended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how often the health check should be executed (the default being 30 seconds).
Each backend must respond to the health check within 5 seconds.
By default, the port of the backend server is used, however, this may be overridden.
@ -438,43 +495,6 @@ Additional http headers and hostname to healthcheck request can be specified, fo
myheader2 = "bar"
```
### Servers
Servers are simply defined using a `url`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
!!! note
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
Here is an example of backends and servers definition:
```toml
[backends]
[backends.backend1]
[backends.backend1.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[backends.backend1.servers.server1]
url = "http://172.17.0.2:80"
weight = 10
[backends.backend1.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
[backends.backend2]
[backends.backend2.LoadBalancer]
method = "drr"
[backends.backend2.servers.server1]
url = "http://172.17.0.4:80"
weight = 1
[backends.backend2.servers.server2]
url = "http://172.17.0.5:80"
weight = 2
```
- Two backends are defined: `backend1` and `backend2`
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
## Configuration
Træfik's configuration has two parts:

View file

@ -196,6 +196,7 @@ Labels can be used on containers to override default behavior.
| Label | Description |
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.docker.network` | Set the docker network to use for connections to this container. [1] |
| `traefik.domain` | Default domain used for frontend rules. |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.protocol=https` | Override the default `http` protocol |
@ -285,6 +286,7 @@ Segment labels override the default behavior.
| Label | Description |
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
| `traefik.<segment_name>.domain` | Default domain used for frontend rules. |
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the segment labels could be used. |
| `traefik.<segment_name>.protocol` | Overrides `traefik.protocol`. |
| `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |

View file

@ -33,6 +33,7 @@ clusters = ["default"]
watch = true
# Default domain used.
# Can be overridden by setting the "traefik.domain" label.
#
# Optional
# Default: ""
@ -135,6 +136,7 @@ Labels can be used on task containers to override default behaviour:
| Label | Description |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Default domain used for frontend rules. |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Override the default `port` value. Overrides `NetworkBindings` from Docker Container |
| `traefik.protocol=https` | Override the default `http` protocol |

View file

@ -140,19 +140,20 @@ Træfik can be configured with a file.
# ...
```
## Configuration mode
## Configuration Mode
You have three choices:
You have two choices:
- [Simple](/configuration/backends/file/#simple)
- [Rules in a Separate File](/configuration/backends/file/#rules-in-a-separate-file)
- [Multiple `.toml` Files](/configuration/backends/file/#multiple-toml-files)
- [Rules in Træfik configuration file](/configuration/backends/file/#rules-in-trfik-configuration-file)
- [Rules in dedicated files](/configuration/backends/file/#rules-in-dedicated-files)
To enable the file backend, you must either pass the `--file` option to the Træfik binary or put the `[file]` section (with or without inner settings) in the configuration file.
The configuration file allows managing both backends/frontends and HTTPS certificates (which are not [Let's Encrypt](https://letsencrypt.org) certificates generated through Træfik).
### Simple
TOML templating can be used if rules are not defined in the Træfik configuration file.
### Rules in Træfik Configuration File
Add your configuration at the end of the global configuration file `traefik.toml`:
@ -197,9 +198,16 @@ defaultEntryPoints = ["http", "https"]
Adding certificates directly to the entryPoint is still maintained but certificates declared in this way cannot be managed dynamically.
It's recommended to use the file provider to declare certificates.
### Rules in a Separate File
!!! warning
TOML templating cannot be used if rules are defined in the Træfik configuration file.
Put your rules in a separate file, for example `rules.toml`:
### Rules in Dedicated Files
Træfik allows defining rules in one or more separate files.
#### One Separate File
You have to specify the file path in the `file.filename` option.
```toml
# traefik.toml
@ -213,8 +221,31 @@ defaultEntryPoints = ["http", "https"]
[file]
filename = "rules.toml"
watch = true
```
The option `file.watch` allows Træfik to watch file changes automatically.
#### Multiple Separated Files
You could have multiple `.toml` files in a directory (and recursively in its sub-directories):
```toml
[file]
directory = "/path/to/config/"
watch = true
```
The option `file.watch` allows Træfik to watch file changes automatically.
#### Separate Files Content
If you are defining rules in one or more separate files, you can use two formats.
##### Simple Format
Backends, Frontends and TLS certificates are defined one at time, as described in the file `rules.toml`:
```toml
# rules.toml
[backends]
@ -239,18 +270,34 @@ defaultEntryPoints = ["http", "https"]
# ...
```
### Multiple `.toml` Files
##### TOML Templating
You could have multiple `.toml` files in a directory (and recursively in its sub-directories):
!!! warning
TOML templating can only be used **if rules are defined in one or more separate files**.
Templating will not work in the Træfik configuration file.
Træfik allows using TOML templating.
Thus, it's possible to define easily lot of Backends, Frontends and TLS certificates as described in the file `template-rules.toml` :
```toml
[file]
directory = "/path/to/config/"
```
# template-rules.toml
[backends]
{{ range $i, $e := until 100 }}
[backends.backend{{ $e }}]
#...
{{ end }}
If you want Træfik to watch file changes automatically, just add:
[frontends]
{{ range $i, $e := until 100 }}
[frontends.frontend{{ $e }}]
#...
{{ end }}
```toml
[file]
watch = true
# HTTPS certificate
{{ range $i, $e := until 100 }}
[[tls]]
#...
{{ end }}
```

View file

@ -239,7 +239,7 @@ The following security annotations are applicable on the Ingress object:
### Authentication
Is possible to add additional authentication annotations to the Ingress object.
Additional authentication annotations can be added to the Ingress object.
The source of the authentication is a Secret object that contains the credentials.
| Annotation | Description |
@ -253,3 +253,12 @@ The following limitations hold:
- The realm is not configurable; the only supported (and default) value is `traefik`.
- The Secret must contain a single file only.
### TLS certificates management
TLS certificates can be managed in Secrets objects.
More information are available in the [User Guide](/user-guide/kubernetes/#add-a-tls-certificate-to-the-ingress).
!!! note
Only TLS certificates provided by users can be stored in Kubernetes Secrets.
[Let's Encrypt](https://letsencrypt.org) certificates cannot be managed in Kubernets Secrets yet.

View file

@ -171,6 +171,7 @@ The following labels can be defined on Marathon applications. They adjust the be
| Label | Description |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Default domain used for frontend rules. |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.portIndex=1` | Register port by index in the application's ports array. Useful when the application exposes multiple ports. |
@ -256,6 +257,7 @@ Segment labels override the default behavior.
| Label | Description |
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
| `traefik.<segment_name>.domain` | Default domain used for frontend rules. |
| `traefik.<segment_name>.portIndex=1` | Create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. |
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
| `traefik.<segment_name>.protocol=http` | Overrides `traefik.protocol`. |

View file

@ -108,6 +108,7 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for
| Label | Description |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Default domain used for frontend rules. |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.portIndex=1` | Register port by index in the application's ports array. Useful when the application exposes multiple ports. |

View file

@ -140,6 +140,7 @@ Labels can be used on task containers to override default behavior:
| Label | Description |
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Default domain used for frontend rules. |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.protocol=https` | Override the default `http` protocol |
@ -223,6 +224,7 @@ Segment labels override the default behavior.
| Label | Description |
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
| `traefik.<segment_name>.domain` | Default domain used for frontend rules. |
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the segment labels could be used. |
| `traefik.<segment_name>.protocol` | Overrides `traefik.protocol`. |
| `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |

View file

@ -61,7 +61,7 @@ Here is an example of an extension setting Træfik labels:
<Extension Name="Traefik">
<Labels xmlns="http://schemas.microsoft.com/2015/03/fabact-no-schema">
<Label Key="traefik.frontend.rule.example2">PathPrefixStrip: /a/path/to/strip</Label>
<Label Key="traefik.enable">true</Label>
<Label Key="traefik.enable">true</Label>
<Label Key="traefik.frontend.passHostHeader">true</Label>
</Labels>
</Extension>

View file

@ -120,7 +120,7 @@ Compress:true
WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16
WhiteList.UseXForwardedFor:true
ProxyProtocol.TrustedIPs:192.168.0.1
ProxyProtocol.Insecure:tue
ProxyProtocol.Insecure:true
ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24
Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0
Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e

View file

@ -48,11 +48,14 @@ Træfik supports two backends: Jaeger and Zipkin.
# Local Agent Host Port instructs reporter to send spans to jaeger-agent at this address
#
# Default: "127.0.0.1:6832"
# Default: "127.0.0.1:6831"
#
localAgentHostPort = "127.0.0.1:6832"
localAgentHostPort = "127.0.0.1:6831"
```
!!! warning
Træfik is only able to send data over compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent).
## Zipkin
```toml

View file

@ -19,13 +19,14 @@ Telling Træfik where your orchestrator is could be the _only_ configuration ste
Imagine that you have deployed a bunch of microservices with the help of an orchestrator (like Swarm or Kubernetes) or a service registry (like etcd or consul).
Now you want users to access these microservices, and you need a reverse proxy.
Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice. In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious.
Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice.
In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious.
**This is when Træfik can help you!**
Træfik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part.
Træfik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part.
**Run Træfik and let it do the work for you!**
**Run Træfik and let it do the work for you!**
_(But if you'd rather configure some of your routes manually, Træfik supports that too!)_
![Architecture](img/architecture.png)
@ -90,19 +91,19 @@ services:
Start your `reverse-proxy` with the following command:
```shell
docker-compose up -d reverse-proxy
docker-compose up -d reverse-proxy
```
You can open a browser and go to [http://localhost:8080](http://localhost:8080) to see Træfik's dashboard (we'll go back there once we have launched a service in step 2).
### 2 — Launch a Service — Træfik Detects It and Creates a Route for You
### 2 — Launch a Service — Træfik Detects It and Creates a Route for You
Now that we have a Træfik instance up and running, we will deploy new services.
Now that we have a Træfik instance up and running, we will deploy new services.
Edit your `docker-compose.yml` file and add the following at the end of your file.
Edit your `docker-compose.yml` file and add the following at the end of your file.
```yaml
# ...
# ...
whoami:
image: emilevauge/whoami #A container that exposes an API to show it's IP address
labels:
@ -112,7 +113,7 @@ Edit your `docker-compose.yml` file and add the following at the end of your fil
The above defines `whoami`: a simple web service that outputs information about the machine it is deployed on (its IP address, host, and so on).
Start the `whoami` service with the following command:
```shell
docker-compose up -d whoami
```
@ -135,9 +136,9 @@ IP: 172.27.0.3
### 3 — Launch More Instances — Traefik Load Balances Them
Run more instances of your `whoami` service with the following command:
```shell
docker-compose up -d --scale whoami=2
docker-compose up -d --scale whoami=2
```
Go back to your browser ([http://localhost:8080](http://localhost:8080)) and see that Træfik has automatically detected the new instance of the container.
@ -164,9 +165,10 @@ IP: 172.27.0.4
### 4 — Enjoy Træfik's Magic
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Træfik work for you! Whatever your infrastructure is, there is probably [an available Træfik backend](https://docs.traefik.io/configuration/backends/available) that will do the job.
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](/) and let Træfik work for you!
Whatever your infrastructure is, there is probably [an available Træfik backend](/#supported-backends) that will do the job.
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](/user-guide/docker-and-lets-encrypt/).
## Resources
@ -196,4 +198,4 @@ Using the tiny Docker image:
```shell
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
```
```

View file

@ -101,6 +101,7 @@ IP: 172.27.0.4
### 4 — Enjoy Træfik's Magic
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Træfik work for you! Whatever your infrastructure is, there is probably [an available Træfik backend](https://docs.traefik.io/configuration/backends/available) that will do the job.
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Træfik work for you!
Whatever your infrastructure is, there is probably [an available Træfik backend](https://docs.traefik.io/#supported-backends) that will do the job.
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).

View file

@ -194,12 +194,14 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
c.Assert(err, checker.IsNil)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/test2", nil)
try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test2.localhost"
try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)
}

View file

@ -267,23 +267,26 @@ func (l *LogHandler) redactHeaders(headers http.Header, fields logrus.Fields, pr
}
func (l *LogHandler) keepAccessLog(statusCode, retryAttempts int) bool {
switch {
case l.config.Filters == nil:
if l.config.Filters == nil {
// no filters were specified
return true
case len(l.httpCodeRanges) == 0 && l.config.Filters.RetryAttempts == false:
}
if len(l.httpCodeRanges) == 0 && !l.config.Filters.RetryAttempts {
// empty filters were specified, e.g. by passing --accessLog.filters only (without other filter options)
return true
case l.httpCodeRanges.Contains(statusCode):
return true
case l.config.Filters.RetryAttempts == true && retryAttempts > 0:
return true
default:
return false
}
}
//-------------------------------------------------------------------------------------------------
if l.httpCodeRanges.Contains(statusCode) {
return true
}
if l.config.Filters.RetryAttempts && retryAttempts > 0 {
return true
}
return false
}
var requestCounter uint64 // Request ID

View file

@ -25,6 +25,7 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
return http.ErrUseLastResponse
},
}
if config.TLS != nil {
tlsConfig, err := config.TLS.CreateTLSConfig()
if err != nil {
@ -32,10 +33,12 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
w.WriteHeader(http.StatusInternalServerError)
return
}
httpClient.Transport = &http.Transport{
TLSClientConfig: tlsConfig,
}
}
forwardReq, err := http.NewRequest(http.MethodGet, config.Address, nil)
tracing.LogRequest(tracing.GetSpan(r), forwardReq)
if err != nil {
@ -68,6 +71,8 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices {
log.Debugf("Remote error %s. StatusCode: %d", config.Address, forwardResponse.StatusCode)
utils.CopyHeaders(w.Header(), forwardResponse.Header)
// Grab the location header, if any.
redirectURL, err := forwardResponse.Location()
@ -79,12 +84,7 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
}
} else if redirectURL.String() != "" {
// Set the location in our response if one was sent back.
w.Header().Add("Location", redirectURL.String())
}
// Pass any Set-Cookie headers the forward auth server provides
for _, cookie := range forwardResponse.Cookies() {
w.Header().Add("Set-Cookie", cookie.String())
w.Header().Set("Location", redirectURL.String())
}
tracing.LogResponseCode(tracing.GetSpan(r), forwardResponse.StatusCode)

View file

@ -11,6 +11,7 @@ import (
"github.com/containous/traefik/testhelpers"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/negroni"
)
@ -110,7 +111,6 @@ func TestForwardAuthRedirect(t *testing.T) {
assert.Equal(t, http.StatusFound, res.StatusCode, "they should be equal")
location, err := res.Location()
assert.NoError(t, err, "there should be no error")
assert.Equal(t, "http://example.com/redirect-test", location.String(), "they should be equal")
@ -119,10 +119,11 @@ func TestForwardAuthRedirect(t *testing.T) {
assert.NotEmpty(t, string(body), "there should be something in the body")
}
func TestForwardAuthCookie(t *testing.T) {
func TestForwardAuthFailResponseHeaders(t *testing.T) {
authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie := &http.Cookie{Name: "example", Value: "testing", Path: "/"}
http.SetCookie(w, cookie)
w.Header().Add("X-Foo", "bar")
http.Error(w, "Forbidden", http.StatusForbidden)
}))
defer authTs.Close()
@ -142,23 +143,36 @@ func TestForwardAuthCookie(t *testing.T) {
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
client := &http.Client{}
res, err := client.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusForbidden, res.StatusCode, "they should be equal")
require.Len(t, res.Cookies(), 1)
for _, cookie := range res.Cookies() {
assert.Equal(t, "testing", cookie.Value, "they should be equal")
}
expectedHeaders := http.Header{
"Content-Length": []string{"10"},
"Content-Type": []string{"text/plain; charset=utf-8"},
"X-Foo": []string{"bar"},
"Set-Cookie": []string{"example=testing; Path=/"},
"X-Content-Type-Options": []string{"nosniff"},
}
assert.Len(t, res.Header, 6)
for key, value := range expectedHeaders {
assert.Equal(t, value, res.Header[key])
}
body, err := ioutil.ReadAll(res.Body)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, "Forbidden\n", string(body), "they should be equal")
}
func Test_writeHeader(t *testing.T) {
testCases := []struct {
name string
headers map[string]string

View file

@ -3,15 +3,17 @@ package errorpages
import (
"bufio"
"bytes"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/containous/traefik/log"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/types"
"github.com/pkg/errors"
"github.com/vulcand/oxy/forward"
"github.com/vulcand/oxy/utils"
)
@ -75,8 +77,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
recorder := newResponseRecorder(w)
next.ServeHTTP(recorder, req)
w.WriteHeader(recorder.GetCode())
// check the recorder code against the configured http status code ranges
for _, block := range h.httpCodeRanges {
if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] {
@ -88,20 +88,43 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
query = strings.Replace(query, "{status}", strconv.Itoa(recorder.GetCode()), -1)
}
if newReq, err := http.NewRequest(http.MethodGet, h.backendURL+query, nil); err != nil {
pageReq, err := newRequest(h.backendURL + query)
if err != nil {
log.Error(err)
w.WriteHeader(recorder.GetCode())
w.Write([]byte(http.StatusText(recorder.GetCode())))
} else {
h.backendHandler.ServeHTTP(w, newReq)
return
}
utils.CopyHeaders(pageReq.Header, req.Header)
utils.CopyHeaders(w.Header(), recorder.Header())
w.WriteHeader(recorder.GetCode())
h.backendHandler.ServeHTTP(w, pageReq)
return
}
}
// did not catch a configured status code so proceed with the request
utils.CopyHeaders(w.Header(), recorder.Header())
w.WriteHeader(recorder.GetCode())
w.Write(recorder.GetBody().Bytes())
}
func newRequest(baseURL string) (*http.Request, error) {
u, err := url.Parse(baseURL)
if err != nil {
return nil, fmt.Errorf("error pages: error when parse URL: %v", err)
}
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, fmt.Errorf("error pages: error when create query: %v", err)
}
req.RequestURI = u.RequestURI()
return req, nil
}
type responseRecorder interface {
http.ResponseWriter
http.Flusher

View file

@ -65,7 +65,7 @@ func TestHandler(t *testing.T) {
errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503-503"}},
backendCode: http.StatusServiceUnavailable,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.RequestURI() == "/"+strconv.Itoa(503) {
if r.RequestURI == "/503" {
fmt.Fprintln(w, "My 503 page.")
} else {
fmt.Fprintln(w, "Failed")
@ -82,7 +82,7 @@ func TestHandler(t *testing.T) {
errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503"}},
backendCode: http.StatusServiceUnavailable,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.RequestURI() == "/"+strconv.Itoa(503) {
if r.RequestURI == "/503" {
fmt.Fprintln(w, "My 503 page.")
} else {
fmt.Fprintln(w, "Failed")
@ -239,7 +239,7 @@ func TestHandlerOldWay(t *testing.T) {
func TestHandlerOldWayIntegration(t *testing.T) {
errorPagesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.RequestURI() == "/"+strconv.Itoa(503) {
if r.URL.RequestURI() == "/503" {
fmt.Fprintln(w, "My 503 page.")
} else {
fmt.Fprintln(w, "Test Server")
@ -318,6 +318,7 @@ func TestHandlerOldWayIntegration(t *testing.T) {
require.NoError(t, err)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Foo", "bar")
w.WriteHeader(test.backendCode)
fmt.Fprintln(w, http.StatusText(test.backendCode))
})
@ -330,6 +331,7 @@ func TestHandlerOldWayIntegration(t *testing.T) {
n.ServeHTTP(recorder, req)
test.validate(t, recorder)
assert.Equal(t, "bar", recorder.Header().Get("X-Foo"), "missing header")
})
}
}

View file

@ -38,21 +38,15 @@ func NewIPWhiteLister(whiteList []string, useXForwardedFor bool) (*IPWhiteLister
}
func (wl *IPWhiteLister) handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
allowed, ip, err := wl.whiteLister.IsAuthorized(r)
err := wl.whiteLister.IsAuthorized(r)
if err != nil {
tracing.SetErrorAndDebugLog(r, "request %+v matched none of the white list - rejecting", r)
tracing.SetErrorAndDebugLog(r, "request %+v - rejecting: %v", r, err)
reject(w)
return
}
if allowed {
tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister)
next.ServeHTTP(w, r)
return
}
tracing.SetErrorAndDebugLog(r, "source-IP %s matched none of the white list - rejecting", ip)
reject(w)
tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister)
next.ServeHTTP(w, r)
}
func (wl *IPWhiteLister) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
@ -63,5 +57,8 @@ func reject(w http.ResponseWriter) {
statusCode := http.StatusForbidden
w.WriteHeader(statusCode)
w.Write([]byte(http.StatusText(statusCode)))
_, err := w.Write([]byte(http.StatusText(statusCode)))
if err != nil {
log.Error(err)
}
}

View file

@ -88,6 +88,13 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
xForwardedFor: []string{"30.30.30.30", "40.40.40.40"},
expected: 200,
},
{
desc: "authorized with only one X-Forwarded-For",
whiteList: []string{"30.30.30.30"},
useXForwardedFor: true,
xForwardedFor: []string{"30.30.30.30"},
expected: 200,
},
{
desc: "non authorized with X-Forwarded-For",
whiteList: []string{"30.30.30.30"},

View file

@ -73,7 +73,10 @@ func (t *Tracing) IsEnabled() bool {
// Close tracer
func (t *Tracing) Close() {
if t.closer != nil {
t.closer.Close()
err := t.closer.Close()
if err != nil {
log.Warn(err)
}
}
}
@ -104,10 +107,13 @@ func GetSpan(r *http.Request) opentracing.Span {
// InjectRequestHeaders used to inject OpenTracing headers into the request
func InjectRequestHeaders(r *http.Request) {
if span := GetSpan(r); span != nil {
opentracing.GlobalTracer().Inject(
err := opentracing.GlobalTracer().Inject(
span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(r.Header))
if err != nil {
log.Error(err)
}
}
}

View file

@ -19,7 +19,7 @@ type Account struct {
const (
// RegistrationURLPathV1Regexp is a regexp which match ACME registration URL in the V1 format
RegistrationURLPathV1Regexp string = `^.*/acme/reg/\d+$`
RegistrationURLPathV1Regexp = `^.*/acme/reg/\d+$`
)
// NewAccount creates an account

View file

@ -330,7 +330,6 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
safe.Go(func() {
if _, err := p.resolveCertificate(domain, true); err != nil {
log.Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
} else {
}
})
}
@ -384,15 +383,6 @@ func (p *Provider) watchCertificate() {
})
}
func (p *Provider) deleteCertificateForDomain(domain types.Domain) {
for k, cert := range p.certificates {
if reflect.DeepEqual(cert.Domain, domain) {
p.certificates = append(p.certificates[:k], p.certificates[k+1:]...)
}
}
p.saveCertificates()
}
func (p *Provider) saveCertificates() {
err := p.Store.SaveCertificates(p.certificates)
if err != nil {

View file

@ -237,19 +237,6 @@ func hasTag(name string, tags []string) bool {
return false
}
func hasTagPrefix(name string, tags []string) bool {
lowerName := strings.ToLower(name)
for _, tag := range tags {
lowerTag := strings.ToLower(tag)
if strings.HasPrefix(lowerTag, lowerName) {
return true
}
}
return false
}
func getTag(name string, tags []string, defaultValue string) string {
lowerName := strings.ToLower(name)

View file

@ -156,17 +156,6 @@ func (p *Provider) getFuncSliceAttribute(name string) func(tags []string) []stri
}
}
// Deprecated
func (p *Provider) getMapAttribute(name string, tags []string) map[string]string {
rawValue := getTag(p.getPrefixedName(name), tags, "")
if len(rawValue) == 0 {
return nil
}
return label.ParseMapValue(p.getPrefixedName(name), rawValue)
}
// Deprecated
func (p *Provider) getFuncIntAttribute(name string, defaultValue int) func(tags []string) int {
return func(tags []string) int {
@ -180,13 +169,6 @@ func (p *Provider) getFuncBoolAttribute(name string, defaultValue bool) func(tag
}
}
// Deprecated
func (p *Provider) getFuncHasAttributePrefix(name string) func(tags []string) bool {
return func(tags []string) bool {
return p.hasAttributePrefix(name, tags)
}
}
// Deprecated
func (p *Provider) getInt64Attribute(name string, tags []string, defaultValue int64) int64 {
rawValue := getTag(p.getPrefixedName(name), tags, "")
@ -244,7 +226,3 @@ func (p *Provider) getBoolAttribute(name string, tags []string, defaultValue boo
}
return value
}
func (p *Provider) hasAttributePrefix(name string, tags []string) bool {
return hasTagPrefix(p.getPrefixedName(name), tags)
}

View file

@ -182,19 +182,20 @@ func (p *Provider) getFrontendRule(container dockerData, segmentLabels map[strin
return value
}
domain := label.GetStringValue(segmentLabels, label.TraefikDomain, p.Domain)
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + p.Domain
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + domain
}
if len(p.Domain) > 0 {
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain
if len(domain) > 0 {
return "Host:" + getSubDomain(container.ServiceName) + "." + domain
}
return ""
}
func (p Provider) getIPAddress(container dockerData) string {
if value := label.GetStringValue(container.Labels, labelDockerNetwork, ""); value != "" {
networkSettings := container.NetworkSettings
if networkSettings.Networks != nil {
@ -246,6 +247,8 @@ func (p Provider) getIPAddress(container dockerData) string {
for _, network := range container.NetworkSettings.Networks {
return network.Addr
}
log.Warnf("Unable to find the IP address for the container %q.", container.Name)
return ""
}
@ -314,12 +317,17 @@ func (p *Provider) getServers(containers []dockerData) map[string]types.Server {
var servers map[string]types.Server
for i, container := range containers {
ip := p.getIPAddress(container)
if len(ip) == 0 {
log.Warnf("Unable to find the IP address for the container %q: the server is ignored.", container.Name)
continue
}
if servers == nil {
servers = make(map[string]types.Server)
}
protocol := label.GetStringValue(container.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
ip := p.getIPAddress(container)
port := getPort(container)
serverName := "server-" + container.SegmentName + "-" + container.Name

View file

@ -406,6 +406,7 @@ func TestDockerBuildConfiguration(t *testing.T) {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var dockerDataList []dockerData
for _, cont := range test.containers {
dData := parseContainer(cont)
@ -809,15 +810,19 @@ func TestDockerGetFrontendRule(t *testing.T) {
expected: "Host:foo.docker.localhost",
},
{
container: containerJSON(name("bar")),
expected: "Host:bar.docker.localhost",
container: containerJSON(name("foo"),
labels(map[string]string{
label.TraefikDomain: "traefik.localhost",
})),
expected: "Host:foo.traefik.localhost",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
})),
expected: "Host:foo.bar",
}, {
},
{
container: containerJSON(labels(map[string]string{
"com.docker.compose.project": "foo",
"com.docker.compose.service": "bar",
@ -1022,3 +1027,122 @@ func TestDockerGetPort(t *testing.T) {
})
}
}
func TestDockerGetServers(t *testing.T) {
p := &Provider{}
testCases := []struct {
desc string
containers []docker.ContainerJSON
expected map[string]types.Server
}{
{
desc: "no container",
expected: nil,
},
{
desc: "with a simple container",
containers: []docker.ContainerJSON{
containerJSON(
name("test1"),
withNetwork("testnet", ipv4("10.10.10.10")),
ports(nat.PortMap{
"80/tcp": {},
})),
},
expected: map[string]types.Server{
"server-test1": {
URL: "http://10.10.10.10:80",
Weight: 1,
},
},
},
{
desc: "with several containers",
containers: []docker.ContainerJSON{
containerJSON(
name("test1"),
withNetwork("testnet", ipv4("10.10.10.11")),
ports(nat.PortMap{
"80/tcp": {},
})),
containerJSON(
name("test2"),
withNetwork("testnet", ipv4("10.10.10.12")),
ports(nat.PortMap{
"81/tcp": {},
})),
containerJSON(
name("test3"),
withNetwork("testnet", ipv4("10.10.10.13")),
ports(nat.PortMap{
"82/tcp": {},
})),
},
expected: map[string]types.Server{
"server-test1": {
URL: "http://10.10.10.11:80",
Weight: 1,
},
"server-test2": {
URL: "http://10.10.10.12:81",
Weight: 1,
},
"server-test3": {
URL: "http://10.10.10.13:82",
Weight: 1,
},
},
},
{
desc: "ignore one container because no ip address",
containers: []docker.ContainerJSON{
containerJSON(
name("test1"),
withNetwork("testnet", ipv4("")),
ports(nat.PortMap{
"80/tcp": {},
})),
containerJSON(
name("test2"),
withNetwork("testnet", ipv4("10.10.10.12")),
ports(nat.PortMap{
"81/tcp": {},
})),
containerJSON(
name("test3"),
withNetwork("testnet", ipv4("10.10.10.13")),
ports(nat.PortMap{
"82/tcp": {},
})),
},
expected: map[string]types.Server{
"server-test2": {
URL: "http://10.10.10.12:81",
Weight: 1,
},
"server-test3": {
URL: "http://10.10.10.13:82",
Weight: 1,
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var dockerDataList []dockerData
for _, cont := range test.containers {
dData := parseContainer(cont)
dockerDataList = append(dockerDataList, dData)
}
servers := p.getServers(dockerDataList)
assert.Equal(t, test.expected, servers)
})
}
}

View file

@ -561,8 +561,11 @@ func TestSwarmGetFrontendRule(t *testing.T) {
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceName("bar")),
expected: "Host:bar.docker.localhost",
service: swarmService(serviceName("foo"),
serviceLabels(map[string]string{
label.TraefikDomain: "traefik.localhost",
})),
expected: "Host:foo.traefik.localhost",
networks: map[string]*docker.NetworkResource{},
},
{

View file

@ -27,12 +27,14 @@ func (p Provider) getFrontendRuleV1(container dockerData) string {
return value
}
domain := label.GetStringValue(container.Labels, label.TraefikDomain, p.Domain)
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + p.Domain
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + domain
}
if len(p.Domain) > 0 {
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain
if len(domain) > 0 {
return "Host:" + getSubDomain(container.ServiceName) + "." + domain
}
return ""

View file

@ -752,15 +752,19 @@ func TestDockerGetFrontendRuleV1(t *testing.T) {
expected: "Host:foo.docker.localhost",
},
{
container: containerJSON(name("bar")),
expected: "Host:bar.docker.localhost",
container: containerJSON(name("foo"),
labels(map[string]string{
label.TraefikDomain: "traefik.localhost",
})),
expected: "Host:foo.traefik.localhost",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
})),
expected: "Host:foo.bar",
}, {
},
{
container: containerJSON(labels(map[string]string{
"com.docker.compose.project": "foo",
"com.docker.compose.service": "bar",

View file

@ -527,8 +527,11 @@ func TestSwarmGetFrontendRuleV1(t *testing.T) {
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceName("bar")),
expected: "Host:bar.docker.localhost",
service: swarmService(serviceName("foo"),
serviceLabels(map[string]string{
label.TraefikDomain: "traefik.localhost",
})),
expected: "Host:foo.traefik.localhost",
networks: map[string]*docker.NetworkResource{},
},
{

View file

@ -136,12 +136,6 @@ func getFuncServiceIntLabelV1(labelSuffix string, defaultValue int) func(contain
}
}
// Deprecated
func hasStrictServiceLabelV1(serviceLabels map[string]string, labelSuffix string) bool {
value, ok := serviceLabels[labelSuffix]
return ok && len(value) > 0
}
// Deprecated
func getServiceStringValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue string) string {
if value, ok := serviceLabels[labelSuffix]; ok {
@ -150,23 +144,6 @@ func getServiceStringValueV1(container dockerData, serviceLabels map[string]stri
return label.GetStringValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
// Deprecated
func getStrictServiceStringValueV1(serviceLabels map[string]string, labelSuffix string, defaultValue string) string {
if value, ok := serviceLabels[labelSuffix]; ok {
return value
}
return defaultValue
}
// Deprecated
func getServiceMapValueV1(container dockerData, serviceLabels map[string]string, serviceName string, labelSuffix string) map[string]string {
if value, ok := serviceLabels[labelSuffix]; ok {
lblName := label.GetServiceLabel(labelSuffix, serviceName)
return label.ParseMapValue(lblName, value)
}
return label.GetMapValue(container.Labels, label.Prefix+labelSuffix)
}
// Deprecated
func getServiceSliceValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string) []string {
if value, ok := serviceLabels[labelSuffix]; ok {
@ -197,17 +174,6 @@ func getServiceIntLabelV1(container dockerData, serviceName string, labelSuffix
return label.GetIntValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
// Deprecated
func getServiceInt64ValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue int64) int64 {
if rawValue, ok := serviceLabels[labelSuffix]; ok {
value, err := strconv.ParseInt(rawValue, 10, 64)
if err == nil {
return value
}
}
return label.GetInt64Value(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
// Deprecated
func getServiceLabelsV1(container dockerData, serviceName string) label.SegmentPropertyValues {
return label.ExtractServiceProperties(container.Labels)[serviceName]

View file

@ -405,154 +405,6 @@ func TestDockerGetServiceStringValueV1(t *testing.T) {
}
}
func TestDockerHasStrictServiceLabelV1(t *testing.T) {
testCases := []struct {
desc string
serviceLabels map[string]string
labelSuffix string
expected bool
}{
{
desc: "should return false when service don't have label",
serviceLabels: map[string]string{},
labelSuffix: "",
expected: false,
},
{
desc: "should return true when service have label",
serviceLabels: map[string]string{
"foo": "bar",
},
labelSuffix: "foo",
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := hasStrictServiceLabelV1(test.serviceLabels, test.labelSuffix)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetStrictServiceStringValueV1(t *testing.T) {
testCases := []struct {
desc string
serviceLabels map[string]string
labelSuffix string
defaultValue string
expected string
}{
{
desc: "should return a string when the label exists",
serviceLabels: map[string]string{
"foo": "bar",
},
labelSuffix: "foo",
expected: "bar",
},
{
desc: "should return a string when the label exists and value empty",
serviceLabels: map[string]string{
"foo": "",
},
labelSuffix: "foo",
defaultValue: "cube",
expected: "",
},
{
desc: "should return the default value when the label doesn't exist",
serviceLabels: map[string]string{},
labelSuffix: "foo",
defaultValue: "cube",
expected: "cube",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getStrictServiceStringValueV1(test.serviceLabels, test.labelSuffix, test.defaultValue)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetServiceMapValueV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
serviceLabels map[string]string
serviceName string
labelSuffix string
expected map[string]string
}{
{
desc: "should return when no labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
serviceLabels: map[string]string{},
serviceName: "soo",
labelSuffix: "foo",
expected: nil,
},
{
desc: "should return a map when label exists",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "bir:fii",
})),
serviceLabels: map[string]string{
"foo": "bar:foo",
},
serviceName: "soo",
labelSuffix: "foo",
expected: map[string]string{
"Bar": "foo",
},
},
{
desc: "should return a map when label exists (fallback to container labels)",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "bir:fii",
})),
serviceLabels: map[string]string{
"fo": "bar:foo",
},
serviceName: "soo",
labelSuffix: "foo",
expected: map[string]string{
"Bir": "fii",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getServiceMapValueV1(dData, test.serviceLabels, test.serviceName, test.labelSuffix)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetServiceSliceValueV1(t *testing.T) {
testCases := []struct {
desc string
@ -672,67 +524,6 @@ func TestDockerGetServiceBoolValueV1(t *testing.T) {
}
}
func TestDockerGetServiceInt64ValueV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
serviceLabels map[string]string
labelSuffix string
defaultValue int64
expected int64
}{
{
desc: "should return default value when no label",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
serviceLabels: map[string]string{},
labelSuffix: "foo",
defaultValue: 666,
expected: 666,
},
{
desc: "should return a int64 when label",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "20",
})),
serviceLabels: map[string]string{
"foo": "10",
},
labelSuffix: "foo",
expected: 10,
},
{
desc: "should return a int64 when label (fallback to container labels)",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "20",
})),
serviceLabels: map[string]string{
"fo": "10",
},
labelSuffix: "foo",
expected: 20,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getServiceInt64ValueV1(dData, test.serviceLabels, test.labelSuffix, test.defaultValue)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerCheckPortLabelsV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON

View file

@ -91,7 +91,9 @@ func (p *Provider) filterInstance(i ecsInstance) bool {
}
func (p *Provider) getFrontendRule(i ecsInstance) string {
defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + p.Domain
domain := label.GetStringValue(i.TraefikLabels, label.TraefikDomain, p.Domain)
defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + domain
return label.GetStringValue(i.TraefikLabels, label.TraefikFrontendRule, defaultRule)
}

View file

@ -10,6 +10,16 @@ import (
const testTaskName = "taskID"
func withAppData(app marathon.Application, segmentName string) appData {
segmentProperties := label.ExtractTraefikLabels(stringValueMap(app.Labels))
return appData{
Application: app,
SegmentLabels: segmentProperties[segmentName],
SegmentName: segmentName,
LinkedApps: nil,
}
}
// Functions related to building applications.
func withApplications(apps ...marathon.Application) *marathon.Applications {

View file

@ -210,10 +210,12 @@ func (p *Provider) getFrontendRule(app appData) string {
}
}
domain := label.GetStringValue(app.SegmentLabels, label.TraefikDomain, p.Domain)
if len(app.SegmentName) > 0 {
return "Host:" + strings.ToLower(provider.Normalize(app.SegmentName)) + "." + p.getSubDomain(app.ID) + "." + p.Domain
return "Host:" + strings.ToLower(provider.Normalize(app.SegmentName)) + "." + p.getSubDomain(app.ID) + "." + domain
}
return "Host:" + p.getSubDomain(app.ID) + "." + p.Domain
return "Host:" + p.getSubDomain(app.ID) + "." + domain
}
func getPort(task marathon.Task, app appData) string {
@ -345,6 +347,9 @@ func (p *Provider) getServer(app appData, task marathon.Task) (string, *types.Se
func (p *Provider) getServerHost(task marathon.Task, app appData) (string, error) {
if app.IPAddressPerTask == nil || p.ForceTaskHostname {
if len(task.Host) == 0 {
return "", fmt.Errorf("host is undefined for task %q app %q", task.ID, app.ID)
}
return task.Host, nil
}

View file

@ -1034,7 +1034,7 @@ func TestGetPort(t *testing.T) {
desc string
application marathon.Application
task marathon.Task
serviceName string
segmentName string
expected string
}{
{
@ -1116,23 +1116,23 @@ func TestGetPort(t *testing.T) {
},
{
desc: "multiple task ports with service index available",
application: application(withLabel(label.Prefix+"http.portIndex", "0")),
application: application(withSegmentLabel(label.TraefikPortIndex, "0", "http")),
task: task(taskPorts(80, 443)),
serviceName: "http",
segmentName: "http",
expected: "80",
},
{
desc: "multiple task ports with service port available",
application: application(withLabel(label.Prefix+"https.port", "443")),
application: application(withSegmentLabel(label.TraefikPort, "443", "https")),
task: task(taskPorts(80, 443)),
serviceName: "https",
segmentName: "https",
expected: "443",
},
{
desc: "multiple task ports with services but default port available",
application: application(withLabel(label.Prefix+"http.weight", "100")),
application: application(withSegmentLabel(label.TraefikWeight, "100", "http")),
task: task(taskPorts(80, 443)),
serviceName: "http",
segmentName: "http",
expected: "80",
},
}
@ -1142,7 +1142,7 @@ func TestGetPort(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getPortV1(test.task, test.application, test.serviceName)
actual := getPort(test.task, withAppData(test.application, test.segmentName))
assert.Equal(t, test.expected, actual)
})
@ -1153,7 +1153,7 @@ func TestGetFrontendRule(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
serviceName string
segmentName string
expected string
marathonLBCompatibility bool
}{
@ -1163,6 +1163,15 @@ func TestGetFrontendRule(t *testing.T) {
marathonLBCompatibility: true,
expected: "Host:test.marathon.localhost",
},
{
desc: "label domain",
application: application(
appID("test"),
withLabel(label.TraefikDomain, "traefik.localhost"),
),
marathonLBCompatibility: true,
expected: "Host:test.traefik.localhost",
},
{
desc: "HAProxy vhost available and LB compat disabled",
application: application(
@ -1180,7 +1189,6 @@ func TestGetFrontendRule(t *testing.T) {
},
{
desc: "frontend rule available",
application: application(
withLabel(label.TraefikFrontendRule, "Host:foo.bar"),
withLabel("HAPROXY_0_VHOST", "unused"),
@ -1189,9 +1197,9 @@ func TestGetFrontendRule(t *testing.T) {
expected: "Host:foo.bar",
},
{
desc: "service label existing",
desc: "segment label frontend rule",
application: application(withSegmentLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
serviceName: "app",
segmentName: "app",
marathonLBCompatibility: true,
expected: "Host:foo.bar",
},
@ -1206,7 +1214,7 @@ func TestGetFrontendRule(t *testing.T) {
MarathonLBCompatibility: test.marathonLBCompatibility,
}
actual := p.getFrontendRuleV1(test.application, test.serviceName)
actual := p.getFrontendRule(withAppData(test.application, test.segmentName))
assert.Equal(t, test.expected, actual)
})
@ -1217,7 +1225,7 @@ func TestGetBackendName(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
serviceName string
segmentName string
expected string
}{
{
@ -1231,9 +1239,9 @@ func TestGetBackendName(t *testing.T) {
expected: "backendbar",
},
{
desc: "service label existing",
desc: "segment label existing",
application: application(withSegmentLabel(label.TraefikBackend, "bar", "app")),
serviceName: "app",
segmentName: "app",
expected: "backendbar",
},
}
@ -1245,7 +1253,7 @@ func TestGetBackendName(t *testing.T) {
p := &Provider{}
actual := p.getBackendNameV1(test.application, test.serviceName)
actual := p.getBackendName(withAppData(test.application, test.segmentName))
assert.Equal(t, test.expected, actual)
})
@ -1256,7 +1264,7 @@ func TestGetServers(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
serviceName string
segmentName string
expected map[string]types.Server
}{
{
@ -1304,12 +1312,14 @@ func TestGetServers(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
if test.desc == "should return nil when all hosts are empty" {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := p.getServersV1(test.application, test.serviceName)
actual := p.getServers(withAppData(test.application, test.segmentName))
assert.Equal(t, test.expected, actual)
})
assert.Equal(t, test.expected, actual)
})
}
}
}

View file

@ -138,10 +138,11 @@ func (p *Provider) getFrontendRuleV1(application marathon.Application, serviceNa
}
}
domain := label.GetStringValue(labels, label.SuffixDomain, p.Domain)
if len(serviceName) > 0 {
return "Host:" + strings.ToLower(provider.Normalize(serviceName)) + "." + p.getSubDomain(application.ID) + "." + p.Domain
return "Host:" + strings.ToLower(provider.Normalize(serviceName)) + "." + p.getSubDomain(application.ID) + "." + domain
}
return "Host:" + p.getSubDomain(application.ID) + "." + p.Domain
return "Host:" + p.getSubDomain(application.ID) + "." + domain
}
// Deprecated

View file

@ -760,3 +760,67 @@ func TestGetStickyV1(t *testing.T) {
})
}
}
func TestGetServersV1(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
segmentName string
expected map[string]types.Server
}{
{
desc: "should return nil when no task",
application: application(ipAddrPerTask(80)),
expected: nil,
},
{
desc: "should return nil when all hosts are empty",
application: application(
withTasks(
task(ipAddresses("1.1.1.1"), withTaskID("A"), taskPorts(80)),
task(ipAddresses("1.1.1.2"), withTaskID("B"), taskPorts(80)),
task(ipAddresses("1.1.1.3"), withTaskID("C"), taskPorts(80))),
),
expected: nil,
},
{
desc: "with 3 tasks",
application: application(
ipAddrPerTask(80),
withTasks(
task(ipAddresses("1.1.1.1"), withTaskID("A"), taskPorts(80)),
task(ipAddresses("1.1.1.2"), withTaskID("B"), taskPorts(80)),
task(ipAddresses("1.1.1.3"), withTaskID("C"), taskPorts(80))),
),
expected: map[string]types.Server{
"server-A": {
URL: "http://1.1.1.1:80",
Weight: label.DefaultWeight,
},
"server-B": {
URL: "http://1.1.1.2:80",
Weight: label.DefaultWeight,
},
"server-C": {
URL: "http://1.1.1.3:80",
Weight: label.DefaultWeight,
},
},
},
}
p := &Provider{}
for _, test := range testCases {
test := test
if test.desc == "should return nil when all hosts are empty" {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := p.getServersV1(test.application, test.segmentName)
assert.Equal(t, test.expected, actual)
})
}
}
}

View file

@ -166,7 +166,9 @@ func (p *Provider) getFrontendRule(task taskData) string {
if v := label.GetStringValue(task.TraefikLabels, label.TraefikFrontendRule, ""); len(v) > 0 {
return v
}
return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + p.Domain
domain := label.GetStringValue(task.TraefikLabels, label.TraefikDomain, p.Domain)
return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + domain
}
func (p *Provider) getServers(tasks []taskData) map[string]types.Server {

View file

@ -660,3 +660,50 @@ func TestGetServers(t *testing.T) {
})
}
}
func TestGetFrontendRule(t *testing.T) {
p := Provider{
Domain: "mesos.localhost",
}
testCases := []struct {
desc string
mesosTask taskData
expected string
}{
{
desc: "label missing",
mesosTask: aTaskData("test",
withInfo("foo"),
),
expected: "Host:foo.mesos.localhost",
},
{
desc: "label domain",
mesosTask: aTaskData("test",
withInfo("foo"),
withLabel(label.TraefikDomain, "traefik.localhost"),
),
expected: "Host:foo.traefik.localhost",
},
{
desc: "frontend rule available",
mesosTask: aTaskData("test",
withInfo("foo"),
withLabel(label.TraefikFrontendRule, "Host:foo.bar"),
),
expected: "Host:foo.bar",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rule := p.getFrontendRule(test.mesosTask)
assert.Equal(t, test.expected, rule)
})
}
}

View file

@ -196,7 +196,9 @@ func (p *Provider) getFrontendRuleV1(task state.Task) string {
if v := getStringValueV1(task, label.TraefikFrontendRule, ""); len(v) > 0 {
return v
}
return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + p.Domain
domain := getStringValueV1(task, label.TraefikDomain, p.Domain)
return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + domain
}
// Deprecated

View file

@ -124,7 +124,9 @@ func (p *Provider) serviceFilter(service rancherData) bool {
}
func (p *Provider) getFrontendRule(serviceName string, labels map[string]string) string {
defaultRule := "Host:" + strings.ToLower(strings.Replace(serviceName, "/", ".", -1)) + "." + p.Domain
domain := label.GetStringValue(labels, label.TraefikDomain, p.Domain)
defaultRule := "Host:" + strings.ToLower(strings.Replace(serviceName, "/", ".", -1)) + "." + domain
return label.GetStringValue(labels, label.TraefikFrontendRule, defaultRule)
}
@ -164,6 +166,11 @@ func getServers(service rancherData) map[string]types.Server {
var servers map[string]types.Server
for index, ip := range service.Containers {
if len(ip) == 0 {
log.Warnf("Unable to find the IP address for a container in the service %q: this container is ignored.", service.Name)
continue
}
if servers == nil {
servers = make(map[string]types.Server)
}

View file

@ -729,6 +729,16 @@ func TestProviderGetFrontendRule(t *testing.T) {
},
expected: "Host:foo.rancher.localhost",
},
{
desc: "with domain label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikDomain: "traefik.localhost",
},
},
expected: "Host:test-service.traefik.localhost",
},
{
desc: "host with /",
service: rancherData{
@ -746,26 +756,6 @@ func TestProviderGetFrontendRule(t *testing.T) {
},
expected: "Host:foo.bar.com",
},
{
desc: "with Path label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Path:/test",
},
},
expected: "Path:/test",
},
{
desc: "with PathPrefix label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "PathPrefix:/test2",
},
},
expected: "PathPrefix:/test2",
},
}
for _, test := range testCases {
@ -849,6 +839,18 @@ func TestGetServers(t *testing.T) {
},
expected: nil,
},
{
desc: "should return nil when no server IPs",
service: rancherData{
Labels: map[string]string{
label.TraefikWeight: "7",
},
Containers: []string{""},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should use default weight when invalid weight value",
service: rancherData{

View file

@ -11,20 +11,20 @@ import (
// NewHeaderRewriter Create a header rewriter
func NewHeaderRewriter(trustedIPs []string, insecure bool) (forward.ReqRewriter, error) {
IPs, err := whitelist.NewIP(trustedIPs, insecure, true)
ips, err := whitelist.NewIP(trustedIPs, insecure, true)
if err != nil {
return nil, err
}
h, err := os.Hostname()
hostname, err := os.Hostname()
if err != nil {
h = "localhost"
hostname = "localhost"
}
return &headerRewriter{
secureRewriter: &forward.HeaderRewriter{TrustForwardHeader: true, Hostname: h},
insecureRewriter: &forward.HeaderRewriter{TrustForwardHeader: false, Hostname: h},
ips: IPs,
secureRewriter: &forward.HeaderRewriter{TrustForwardHeader: false, Hostname: hostname},
insecureRewriter: &forward.HeaderRewriter{TrustForwardHeader: true, Hostname: hostname},
ips: ips,
insecure: insecure,
}, nil
}
@ -37,16 +37,17 @@ type headerRewriter struct {
}
func (h *headerRewriter) Rewrite(req *http.Request) {
authorized, _, err := h.ips.IsAuthorized(req)
if h.insecure {
h.insecureRewriter.Rewrite(req)
return
}
err := h.ips.IsAuthorized(req)
if err != nil {
log.Error(err)
h.secureRewriter.Rewrite(req)
return
}
if h.insecure || authorized {
h.secureRewriter.Rewrite(req)
} else {
h.insecureRewriter.Rewrite(req)
}
h.insecureRewriter.Rewrite(req)
}

View file

@ -0,0 +1,104 @@
package server
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHeaderRewriter_Rewrite(t *testing.T) {
testCases := []struct {
desc string
remoteAddr string
trustedIPs []string
insecure bool
expected map[string]string
}{
{
desc: "Secure & authorized",
remoteAddr: "10.10.10.10:80",
trustedIPs: []string{"10.10.10.10"},
insecure: false,
expected: map[string]string{
"X-Foo": "bar",
"X-Forwarded-For": "30.30.30.30",
},
},
{
desc: "Secure & unauthorized",
remoteAddr: "50.50.50.50:80",
trustedIPs: []string{"10.10.10.10"},
insecure: false,
expected: map[string]string{
"X-Foo": "bar",
"X-Forwarded-For": "",
},
},
{
desc: "Secure & authorized error",
remoteAddr: "10.10.10.10",
trustedIPs: []string{"10.10.10.10"},
insecure: false,
expected: map[string]string{
"X-Foo": "bar",
"X-Forwarded-For": "",
},
},
{
desc: "insecure & authorized",
remoteAddr: "10.10.10.10:80",
trustedIPs: []string{"10.10.10.10"},
insecure: true,
expected: map[string]string{
"X-Foo": "bar",
"X-Forwarded-For": "30.30.30.30",
},
},
{
desc: "insecure & unauthorized",
remoteAddr: "50.50.50.50:80",
trustedIPs: []string{"10.10.10.10"},
insecure: true,
expected: map[string]string{
"X-Foo": "bar",
"X-Forwarded-For": "30.30.30.30",
},
},
{
desc: "insecure & authorized error",
remoteAddr: "10.10.10.10",
trustedIPs: []string{"10.10.10.10"},
insecure: true,
expected: map[string]string{
"X-Foo": "bar",
"X-Forwarded-For": "30.30.30.30",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rewriter, err := NewHeaderRewriter(test.trustedIPs, test.insecure)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "http://20.20.20.20/foo", nil)
require.NoError(t, err)
req.RemoteAddr = test.remoteAddr
req.Header.Set("X-Foo", "bar")
req.Header.Set("X-Forwarded-For", "30.30.30.30")
rewriter.Rewrite(req)
for key, value := range test.expected {
assert.Equal(t, value, req.Header.Get(key))
}
})
}
}

View file

@ -767,7 +767,8 @@ func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.
if !ok {
return false, fmt.Errorf("type error %v", addr)
}
return IPs.ContainsIP(ip.IP)
return IPs.ContainsIP(ip.IP), nil
},
}
}

View file

@ -90,12 +90,15 @@ func (f FileOrContent) Read() ([]byte, error) {
func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, error) {
config := &tls.Config{}
domainsCertificates := make(map[string]map[string]*tls.Certificate)
if c.isEmpty() {
config.Certificates = []tls.Certificate{}
cert, err := generate.DefaultCertificate()
if err != nil {
return nil, err
}
config.Certificates = append(config.Certificates, *cert)
} else {
for _, certificate := range *c {
@ -104,8 +107,9 @@ func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, erro
log.Errorf("Unable to add a certificate to the entryPoint %q : %v", entryPointName, err)
continue
}
for _, certDom := range domainsCertificates {
for _, cert := range map[string]*tls.Certificate(certDom) {
for _, cert := range certDom {
config.Certificates = append(config.Certificates, *cert)
}
}

View file

@ -28,34 +28,21 @@ type AccessLog struct {
Fields *AccessLogFields `json:"fields,omitempty" description:"AccessLogFields" export:"true"`
}
// StatusCodes holds status codes ranges to filter access log
type StatusCodes []string
// AccessLogFilters holds filters configuration
type AccessLogFilters struct {
StatusCodes StatusCodes `json:"statusCodes,omitempty" description:"Keep access logs with status codes in the specified range" export:"true"`
RetryAttempts bool `json:"retryAttempts,omitempty" description:"Keep access logs when at least one retry happened" export:"true"`
}
// FieldNames holds maps of fields with specific mode
type FieldNames map[string]string
// AccessLogFields holds configuration for access log fields
type AccessLogFields struct {
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop" export:"true"`
Names FieldNames `json:"names,omitempty" description:"Override mode for fields" export:"true"`
Headers *FieldHeaders `json:"headers,omitempty" description:"Headers to keep, drop or redact" export:"true"`
}
// FieldHeaderNames holds maps of fields with specific mode
type FieldHeaderNames map[string]string
// FieldHeaders holds configuration for access log headers
type FieldHeaders struct {
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop | redact" export:"true"`
Names FieldHeaderNames `json:"names,omitempty" description:"Override mode for headers" export:"true"`
}
// StatusCodes holds status codes ranges to filter access log
type StatusCodes []string
// Set adds strings elem into the the parser
// it splits str on , and ;
func (s *StatusCodes) Set(str string) error {
@ -79,6 +66,9 @@ func (s *StatusCodes) SetValue(val interface{}) {
*s = val.(StatusCodes)
}
// FieldNames holds maps of fields with specific mode
type FieldNames map[string]string
// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (f *FieldNames) String() string {
@ -111,6 +101,9 @@ func (f *FieldNames) SetValue(val interface{}) {
*f = val.(FieldNames)
}
// FieldHeaderNames holds maps of fields with specific mode
type FieldHeaderNames map[string]string
// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (f *FieldHeaderNames) String() string {
@ -141,6 +134,13 @@ func (f *FieldHeaderNames) SetValue(val interface{}) {
*f = val.(FieldHeaderNames)
}
// AccessLogFields holds configuration for access log fields
type AccessLogFields struct {
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop" export:"true"`
Names FieldNames `json:"names,omitempty" description:"Override mode for fields" export:"true"`
Headers *FieldHeaders `json:"headers,omitempty" description:"Headers to keep, drop or redact" export:"true"`
}
// Keep check if the field need to be kept or dropped
func (f *AccessLogFields) Keep(field string) bool {
defaultKeep := true
@ -154,17 +154,6 @@ func (f *AccessLogFields) Keep(field string) bool {
return defaultKeep
}
func checkFieldValue(value string, defaultKeep bool) bool {
switch value {
case AccessLogKeep:
return true
case AccessLogDrop:
return false
default:
return defaultKeep
}
}
// KeepHeader checks if the headers need to be kept, dropped or redacted and returns the status
func (f *AccessLogFields) KeepHeader(header string) string {
defaultValue := AccessLogKeep
@ -178,6 +167,17 @@ func (f *AccessLogFields) KeepHeader(header string) string {
return defaultValue
}
func checkFieldValue(value string, defaultKeep bool) bool {
switch value {
case AccessLogKeep:
return true
case AccessLogDrop:
return false
default:
return defaultKeep
}
}
func checkFieldHeaderValue(value string, defaultValue string) string {
if value == AccessLogKeep || value == AccessLogDrop || value == AccessLogRedact {
return value

View file

@ -1,11 +1,11 @@
package whitelist
import (
"errors"
"fmt"
"net"
"net/http"
"github.com/pkg/errors"
"strings"
)
const (
@ -50,64 +50,78 @@ func NewIP(whiteList []string, insecure bool, useXForwardedFor bool) (*IP, error
}
// IsAuthorized checks if provided request is authorized by the white list
func (ip *IP) IsAuthorized(req *http.Request) (bool, net.IP, error) {
func (ip *IP) IsAuthorized(req *http.Request) error {
if ip.insecure {
return true, nil, nil
return nil
}
var invalidMatches []string
if ip.useXForwardedFor {
xFFs := req.Header[XForwardedFor]
if len(xFFs) > 1 {
if len(xFFs) > 0 {
for _, xFF := range xFFs {
ok, i, err := ip.contains(parseHost(xFF))
ok, err := ip.contains(parseHost(xFF))
if err != nil {
return false, nil, err
return err
}
if ok {
return ok, i, nil
return nil
}
invalidMatches = append(invalidMatches, xFF)
}
}
}
host, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return false, nil, err
return err
}
return ip.contains(host)
ok, err := ip.contains(host)
if err != nil {
return err
}
if !ok {
invalidMatches = append(invalidMatches, req.RemoteAddr)
return fmt.Errorf("%q matched none of the white list", strings.Join(invalidMatches, ", "))
}
return nil
}
// contains checks if provided address is in the white list
func (ip *IP) contains(addr string) (bool, net.IP, error) {
func (ip *IP) contains(addr string) (bool, error) {
ipAddr, err := parseIP(addr)
if err != nil {
return false, nil, fmt.Errorf("unable to parse address: %s: %s", addr, err)
return false, fmt.Errorf("unable to parse address: %s: %s", addr, err)
}
contains, err := ip.ContainsIP(ipAddr)
return contains, ipAddr, err
return ip.ContainsIP(ipAddr), nil
}
// ContainsIP checks if provided address is in the white list
func (ip *IP) ContainsIP(addr net.IP) (bool, error) {
func (ip *IP) ContainsIP(addr net.IP) bool {
if ip.insecure {
return true, nil
return true
}
for _, whiteListIP := range ip.whiteListsIPs {
if whiteListIP.Equal(addr) {
return true, nil
return true
}
}
for _, whiteListNet := range ip.whiteListsNet {
if whiteListNet.Contains(addr) {
return true, nil
return true
}
}
return false, nil
return false
}
func parseIP(addr string) (net.IP, error) {

View file

@ -17,7 +17,7 @@ func TestIsAuthorized(t *testing.T) {
allowXForwardedFor bool
remoteAddr string
xForwardedForValues []string
expected bool
authorized bool
}{
{
desc: "allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor in range",
@ -25,7 +25,7 @@ func TestIsAuthorized(t *testing.T) {
allowXForwardedFor: true,
remoteAddr: "10.2.3.1:123",
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
expected: true,
authorized: true,
},
{
desc: "allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor in range",
@ -33,7 +33,7 @@ func TestIsAuthorized(t *testing.T) {
allowXForwardedFor: true,
remoteAddr: "1.2.3.1:123",
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
expected: true,
authorized: true,
},
{
desc: "allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor not in range",
@ -41,7 +41,7 @@ func TestIsAuthorized(t *testing.T) {
allowXForwardedFor: true,
remoteAddr: "1.2.3.1:123",
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
expected: true,
authorized: true,
},
{
desc: "allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor not in range",
@ -49,7 +49,7 @@ func TestIsAuthorized(t *testing.T) {
allowXForwardedFor: true,
remoteAddr: "10.2.3.1:123",
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
expected: false,
authorized: false,
},
{
desc: "don't allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor in range",
@ -57,7 +57,7 @@ func TestIsAuthorized(t *testing.T) {
allowXForwardedFor: false,
remoteAddr: "10.2.3.1:123",
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
expected: false,
authorized: false,
},
{
desc: "don't allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor in range",
@ -65,7 +65,7 @@ func TestIsAuthorized(t *testing.T) {
allowXForwardedFor: false,
remoteAddr: "1.2.3.1:123",
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
expected: true,
authorized: true,
},
{
desc: "don't allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor not in range",
@ -73,7 +73,7 @@ func TestIsAuthorized(t *testing.T) {
allowXForwardedFor: false,
remoteAddr: "1.2.3.1:123",
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
expected: true,
authorized: true,
},
{
desc: "don't allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor not in range",
@ -81,7 +81,7 @@ func TestIsAuthorized(t *testing.T) {
allowXForwardedFor: false,
remoteAddr: "10.2.3.1:123",
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
expected: false,
authorized: false,
},
}
@ -95,11 +95,12 @@ func TestIsAuthorized(t *testing.T) {
whiteLister, err := NewIP(test.whiteList, false, test.allowXForwardedFor)
require.NoError(t, err)
authorized, ips, err := whiteLister.IsAuthorized(req)
require.NoError(t, err)
assert.NotNil(t, ips)
assert.Equal(t, test.expected, authorized)
err = whiteLister.IsAuthorized(req)
if test.authorized {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}
@ -349,16 +350,14 @@ func TestContainsIsAllowed(t *testing.T) {
require.NotNil(t, whiteLister)
for _, testIP := range test.passIPs {
allowed, ip, err := whiteLister.contains(testIP)
allowed, err := whiteLister.contains(testIP)
require.NoError(t, err)
require.NotNil(t, ip, err)
assert.Truef(t, allowed, "%s should have passed.", testIP)
}
for _, testIP := range test.rejectIPs {
allowed, ip, err := whiteLister.contains(testIP)
allowed, err := whiteLister.contains(testIP)
require.NoError(t, err)
require.NotNil(t, ip, err)
assert.Falsef(t, allowed, "%s should not have passed.", testIP)
}
})
@ -405,7 +404,7 @@ func TestContainsInsecure(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ok, _, err := test.whiteLister.contains(test.ip)
ok, err := test.whiteLister.contains(test.ip)
require.NoError(t, err)
assert.Equal(t, test.expected, ok)
@ -426,9 +425,8 @@ func TestContainsBrokenIPs(t *testing.T) {
require.NoError(t, err)
for _, testIP := range brokenIPs {
_, ip, err := whiteLister.contains(testIP)
_, err := whiteLister.contains(testIP)
assert.Error(t, err)
require.Nil(t, ip, err)
}
}