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", SamplingServerURL: "http://localhost:5778/sampling",
SamplingType: "const", SamplingType: "const",
SamplingParam: 1.0, SamplingParam: 1.0,
LocalAgentHostPort: "127.0.0.1:6832", LocalAgentHostPort: "127.0.0.1:6831",
}, },
Zipkin: &zipkin.Config{ Zipkin: &zipkin.Config{
HTTPEndpoint: "http://localhost:9411/api/v1/spans", HTTPEndpoint: "http://localhost:9411/api/v1/spans",

View file

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

View file

@ -138,7 +138,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
SamplingServerURL: "http://localhost:5778/sampling", SamplingServerURL: "http://localhost:5778/sampling",
SamplingType: "const", SamplingType: "const",
SamplingParam: 1.0, SamplingParam: 1.0,
LocalAgentHostPort: "127.0.0.1:6832", LocalAgentHostPort: "127.0.0.1:6831",
}, },
Zipkin: nil, Zipkin: nil,
}, },
@ -151,7 +151,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
SamplingServerURL: "http://localhost:5778/sampling", SamplingServerURL: "http://localhost:5778/sampling",
SamplingType: "const", SamplingType: "const",
SamplingParam: 1.0, SamplingParam: 1.0,
LocalAgentHostPort: "127.0.0.1:6832", LocalAgentHostPort: "127.0.0.1:6831",
}, },
}, },
expected: &tracing.Tracing{ expected: &tracing.Tracing{
@ -173,7 +173,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
SamplingServerURL: "http://localhost:5778/sampling", SamplingServerURL: "http://localhost:5778/sampling",
SamplingType: "const", SamplingType: "const",
SamplingParam: 1.0, SamplingParam: 1.0,
LocalAgentHostPort: "127.0.0.1:6832", LocalAgentHostPort: "127.0.0.1:6831",
}, },
Zipkin: &zipkin.Config{ Zipkin: &zipkin.Config{
HTTPEndpoint: "http://powpow:9411/api/v1/spans", 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 !!! warning
If the custom header name is the same as one header name of the request or response, it will be replaced. 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 ```toml
[frontends] [frontends]
@ -276,7 +276,7 @@ In this example, all matches to the path `/cheese` will have the `X-Script-Name`
rule = "PathPrefixStrip:/cheese" 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 ```toml
[frontends] [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. 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: Various methods of load-balancing are supported:
- `wrr`: Weighted Round Robin. - `wrr`: Weighted Round Robin.
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. - `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. 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. 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. 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. 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. - `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). - `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. 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. 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] [backends.backend1.maxconn]
amount = 10 amount = 10
extractorfunc = "request.host" 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. - `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. - 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. - 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. Sticky sessions are supported with both load balancers.
When sticky sessions are enabled, a cookie is set on the initial request. 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. 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. If not, a new backend will be assigned.
```toml ```toml
[backends] [backends]
[backends.backend1] [backends.backend1]
@ -395,10 +452,10 @@ The deprecated way:
sticky = true 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. 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. 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. 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" 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 ## Configuration
Træfik's configuration has two parts: 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 | | Label | Description |
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.docker.network` | Set the docker network to use for connections to this container. [1] | | `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.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. | | `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.protocol=https` | Override the default `http` protocol | | `traefik.protocol=https` | Override the default `http` protocol |
@ -285,6 +286,7 @@ Segment labels override the default behavior.
| Label | Description | | 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>.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>.protocol` | Overrides `traefik.protocol`. |
| `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. | | `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |

View file

@ -33,6 +33,7 @@ clusters = ["default"]
watch = true watch = true
# Default domain used. # Default domain used.
# Can be overridden by setting the "traefik.domain" label.
# #
# Optional # Optional
# Default: "" # Default: ""
@ -135,6 +136,7 @@ Labels can be used on task containers to override default behaviour:
| Label | Description | | Label | Description |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Default domain used for frontend rules. |
| `traefik.enable=false` | Disable this container in Træfik | | `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Override the default `port` value. Overrides `NetworkBindings` from Docker Container | | `traefik.port=80` | Override the default `port` value. Overrides `NetworkBindings` from Docker Container |
| `traefik.protocol=https` | Override the default `http` protocol | | `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 Træfik configuration file](/configuration/backends/file/#rules-in-trfik-configuration-file)
- [Rules in a Separate File](/configuration/backends/file/#rules-in-a-separate-file) - [Rules in dedicated files](/configuration/backends/file/#rules-in-dedicated-files)
- [Multiple `.toml` Files](/configuration/backends/file/#multiple-toml-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. 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). 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`: 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. 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. 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 ```toml
# traefik.toml # traefik.toml
@ -213,8 +221,31 @@ defaultEntryPoints = ["http", "https"]
[file] [file]
filename = "rules.toml" 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 ```toml
# rules.toml # rules.toml
[backends] [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 ```toml
[file] # template-rules.toml
directory = "/path/to/config/" [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] # HTTPS certificate
watch = true {{ range $i, $e := until 100 }}
[[tls]]
#...
{{ end }}
``` ```

View file

@ -239,7 +239,7 @@ The following security annotations are applicable on the Ingress object:
### Authentication ### 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. The source of the authentication is a Secret object that contains the credentials.
| Annotation | Description | | Annotation | Description |
@ -253,3 +253,12 @@ The following limitations hold:
- The realm is not configurable; the only supported (and default) value is `traefik`. - The realm is not configurable; the only supported (and default) value is `traefik`.
- The Secret must contain a single file only. - 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 | | Label | Description |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Default domain used for frontend rules. |
| `traefik.enable=false` | Disable this container in Træfik | | `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. | | `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. | | `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 | | 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>.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>.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`. | | `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 | | Label | Description |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Default domain used for frontend rules. |
| `traefik.enable=false` | Disable this container in Træfik | | `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. | | `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. | | `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 | | Label | Description |
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.domain` | Default domain used for frontend rules. |
| `traefik.enable=false` | Disable this container in Træfik | | `traefik.enable=false` | Disable this container in Træfik |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. | | `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.protocol=https` | Override the default `http` protocol | | `traefik.protocol=https` | Override the default `http` protocol |
@ -223,6 +224,7 @@ Segment labels override the default behavior.
| Label | Description | | 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>.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>.protocol` | Overrides `traefik.protocol`. |
| `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. | | `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |

View file

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

View file

@ -19,7 +19,8 @@ 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). 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. 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!** **This is when Træfik can help you!**
@ -164,9 +165,10 @@ IP: 172.27.0.4
### 4 — Enjoy Træfik's Magic ### 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 ## Resources

View file

@ -101,6 +101,7 @@ IP: 172.27.0.4
### 4 — Enjoy Træfik's Magic ### 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/). 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) c.Assert(err, checker.IsNil)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/test2", nil) 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) c.Assert(err, checker.IsNil)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test2.localhost" 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) 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 { func (l *LogHandler) keepAccessLog(statusCode, retryAttempts int) bool {
switch { if l.config.Filters == nil {
case l.config.Filters == nil:
// no filters were specified // no filters were specified
return true 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) // empty filters were specified, e.g. by passing --accessLog.filters only (without other filter options)
return true 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 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 return http.ErrUseLastResponse
}, },
} }
if config.TLS != nil { if config.TLS != nil {
tlsConfig, err := config.TLS.CreateTLSConfig() tlsConfig, err := config.TLS.CreateTLSConfig()
if err != nil { if err != nil {
@ -32,10 +33,12 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
httpClient.Transport = &http.Transport{ httpClient.Transport = &http.Transport{
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
} }
} }
forwardReq, err := http.NewRequest(http.MethodGet, config.Address, nil) forwardReq, err := http.NewRequest(http.MethodGet, config.Address, nil)
tracing.LogRequest(tracing.GetSpan(r), forwardReq) tracing.LogRequest(tracing.GetSpan(r), forwardReq)
if err != nil { 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 { if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices {
log.Debugf("Remote error %s. StatusCode: %d", config.Address, forwardResponse.StatusCode) log.Debugf("Remote error %s. StatusCode: %d", config.Address, forwardResponse.StatusCode)
utils.CopyHeaders(w.Header(), forwardResponse.Header)
// Grab the location header, if any. // Grab the location header, if any.
redirectURL, err := forwardResponse.Location() redirectURL, err := forwardResponse.Location()
@ -79,12 +84,7 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
} }
} else if redirectURL.String() != "" { } else if redirectURL.String() != "" {
// Set the location in our response if one was sent back. // Set the location in our response if one was sent back.
w.Header().Add("Location", redirectURL.String()) w.Header().Set("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())
} }
tracing.LogResponseCode(tracing.GetSpan(r), forwardResponse.StatusCode) tracing.LogResponseCode(tracing.GetSpan(r), forwardResponse.StatusCode)

View file

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

View file

@ -3,15 +3,17 @@ package errorpages
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"errors"
"fmt"
"net" "net"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/middlewares" "github.com/containous/traefik/middlewares"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/pkg/errors"
"github.com/vulcand/oxy/forward" "github.com/vulcand/oxy/forward"
"github.com/vulcand/oxy/utils" "github.com/vulcand/oxy/utils"
) )
@ -75,8 +77,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
recorder := newResponseRecorder(w) recorder := newResponseRecorder(w)
next.ServeHTTP(recorder, req) next.ServeHTTP(recorder, req)
w.WriteHeader(recorder.GetCode())
// check the recorder code against the configured http status code ranges // check the recorder code against the configured http status code ranges
for _, block := range h.httpCodeRanges { for _, block := range h.httpCodeRanges {
if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] { 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) 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()))) w.Write([]byte(http.StatusText(recorder.GetCode())))
} else { return
h.backendHandler.ServeHTTP(w, newReq)
} }
utils.CopyHeaders(pageReq.Header, req.Header)
utils.CopyHeaders(w.Header(), recorder.Header())
w.WriteHeader(recorder.GetCode())
h.backendHandler.ServeHTTP(w, pageReq)
return return
} }
} }
// did not catch a configured status code so proceed with the request // did not catch a configured status code so proceed with the request
utils.CopyHeaders(w.Header(), recorder.Header()) utils.CopyHeaders(w.Header(), recorder.Header())
w.WriteHeader(recorder.GetCode())
w.Write(recorder.GetBody().Bytes()) 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 { type responseRecorder interface {
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher

View file

@ -65,7 +65,7 @@ func TestHandler(t *testing.T) {
errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503-503"}}, errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503-503"}},
backendCode: http.StatusServiceUnavailable, backendCode: http.StatusServiceUnavailable,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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.") fmt.Fprintln(w, "My 503 page.")
} else { } else {
fmt.Fprintln(w, "Failed") fmt.Fprintln(w, "Failed")
@ -82,7 +82,7 @@ func TestHandler(t *testing.T) {
errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503"}}, errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503"}},
backendCode: http.StatusServiceUnavailable, backendCode: http.StatusServiceUnavailable,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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.") fmt.Fprintln(w, "My 503 page.")
} else { } else {
fmt.Fprintln(w, "Failed") fmt.Fprintln(w, "Failed")
@ -239,7 +239,7 @@ func TestHandlerOldWay(t *testing.T) {
func TestHandlerOldWayIntegration(t *testing.T) { func TestHandlerOldWayIntegration(t *testing.T) {
errorPagesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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.") fmt.Fprintln(w, "My 503 page.")
} else { } else {
fmt.Fprintln(w, "Test Server") fmt.Fprintln(w, "Test Server")
@ -318,6 +318,7 @@ func TestHandlerOldWayIntegration(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Foo", "bar")
w.WriteHeader(test.backendCode) w.WriteHeader(test.backendCode)
fmt.Fprintln(w, http.StatusText(test.backendCode)) fmt.Fprintln(w, http.StatusText(test.backendCode))
}) })
@ -330,6 +331,7 @@ func TestHandlerOldWayIntegration(t *testing.T) {
n.ServeHTTP(recorder, req) n.ServeHTTP(recorder, req)
test.validate(t, recorder) 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) { 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 { 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) reject(w)
return return
} }
if allowed {
tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister) tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister)
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return
}
tracing.SetErrorAndDebugLog(r, "source-IP %s matched none of the white list - rejecting", ip)
reject(w)
} }
func (wl *IPWhiteLister) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 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 statusCode := http.StatusForbidden
w.WriteHeader(statusCode) 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"}, xForwardedFor: []string{"30.30.30.30", "40.40.40.40"},
expected: 200, 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", desc: "non authorized with X-Forwarded-For",
whiteList: []string{"30.30.30.30"}, whiteList: []string{"30.30.30.30"},

View file

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

View file

@ -19,7 +19,7 @@ type Account struct {
const ( const (
// RegistrationURLPathV1Regexp is a regexp which match ACME registration URL in the V1 format // 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 // NewAccount creates an account

View file

@ -330,7 +330,6 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
safe.Go(func() { safe.Go(func() {
if _, err := p.resolveCertificate(domain, true); err != nil { if _, err := p.resolveCertificate(domain, true); err != nil {
log.Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err) 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() { func (p *Provider) saveCertificates() {
err := p.Store.SaveCertificates(p.certificates) err := p.Store.SaveCertificates(p.certificates)
if err != nil { if err != nil {

View file

@ -237,19 +237,6 @@ func hasTag(name string, tags []string) bool {
return false 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 { func getTag(name string, tags []string, defaultValue string) string {
lowerName := strings.ToLower(name) 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 // Deprecated
func (p *Provider) getFuncIntAttribute(name string, defaultValue int) func(tags []string) int { func (p *Provider) getFuncIntAttribute(name string, defaultValue int) func(tags []string) int {
return 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 // Deprecated
func (p *Provider) getInt64Attribute(name string, tags []string, defaultValue int64) int64 { func (p *Provider) getInt64Attribute(name string, tags []string, defaultValue int64) int64 {
rawValue := getTag(p.getPrefixedName(name), tags, "") rawValue := getTag(p.getPrefixedName(name), tags, "")
@ -244,7 +226,3 @@ func (p *Provider) getBoolAttribute(name string, tags []string, defaultValue boo
} }
return value 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 return value
} }
domain := label.GetStringValue(segmentLabels, label.TraefikDomain, p.Domain)
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil { 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 { if len(domain) > 0 {
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain return "Host:" + getSubDomain(container.ServiceName) + "." + domain
} }
return "" return ""
} }
func (p Provider) getIPAddress(container dockerData) string { func (p Provider) getIPAddress(container dockerData) string {
if value := label.GetStringValue(container.Labels, labelDockerNetwork, ""); value != "" { if value := label.GetStringValue(container.Labels, labelDockerNetwork, ""); value != "" {
networkSettings := container.NetworkSettings networkSettings := container.NetworkSettings
if networkSettings.Networks != nil { if networkSettings.Networks != nil {
@ -246,6 +247,8 @@ func (p Provider) getIPAddress(container dockerData) string {
for _, network := range container.NetworkSettings.Networks { for _, network := range container.NetworkSettings.Networks {
return network.Addr return network.Addr
} }
log.Warnf("Unable to find the IP address for the container %q.", container.Name)
return "" return ""
} }
@ -314,12 +317,17 @@ func (p *Provider) getServers(containers []dockerData) map[string]types.Server {
var servers map[string]types.Server var servers map[string]types.Server
for i, container := range containers { 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 { if servers == nil {
servers = make(map[string]types.Server) servers = make(map[string]types.Server)
} }
protocol := label.GetStringValue(container.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol) protocol := label.GetStringValue(container.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
ip := p.getIPAddress(container)
port := getPort(container) port := getPort(container)
serverName := "server-" + container.SegmentName + "-" + container.Name serverName := "server-" + container.SegmentName + "-" + container.Name

View file

@ -406,6 +406,7 @@ func TestDockerBuildConfiguration(t *testing.T) {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
var dockerDataList []dockerData var dockerDataList []dockerData
for _, cont := range test.containers { for _, cont := range test.containers {
dData := parseContainer(cont) dData := parseContainer(cont)
@ -809,15 +810,19 @@ func TestDockerGetFrontendRule(t *testing.T) {
expected: "Host:foo.docker.localhost", expected: "Host:foo.docker.localhost",
}, },
{ {
container: containerJSON(name("bar")), container: containerJSON(name("foo"),
expected: "Host:bar.docker.localhost", labels(map[string]string{
label.TraefikDomain: "traefik.localhost",
})),
expected: "Host:foo.traefik.localhost",
}, },
{ {
container: containerJSON(labels(map[string]string{ container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "Host:foo.bar", label.TraefikFrontendRule: "Host:foo.bar",
})), })),
expected: "Host:foo.bar", expected: "Host:foo.bar",
}, { },
{
container: containerJSON(labels(map[string]string{ container: containerJSON(labels(map[string]string{
"com.docker.compose.project": "foo", "com.docker.compose.project": "foo",
"com.docker.compose.service": "bar", "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{}, networks: map[string]*docker.NetworkResource{},
}, },
{ {
service: swarmService(serviceName("bar")), service: swarmService(serviceName("foo"),
expected: "Host:bar.docker.localhost", serviceLabels(map[string]string{
label.TraefikDomain: "traefik.localhost",
})),
expected: "Host:foo.traefik.localhost",
networks: map[string]*docker.NetworkResource{}, networks: map[string]*docker.NetworkResource{},
}, },
{ {

View file

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

View file

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

View file

@ -527,8 +527,11 @@ func TestSwarmGetFrontendRuleV1(t *testing.T) {
networks: map[string]*docker.NetworkResource{}, networks: map[string]*docker.NetworkResource{},
}, },
{ {
service: swarmService(serviceName("bar")), service: swarmService(serviceName("foo"),
expected: "Host:bar.docker.localhost", serviceLabels(map[string]string{
label.TraefikDomain: "traefik.localhost",
})),
expected: "Host:foo.traefik.localhost",
networks: map[string]*docker.NetworkResource{}, 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 // Deprecated
func getServiceStringValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue string) string { func getServiceStringValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue string) string {
if value, ok := serviceLabels[labelSuffix]; ok { 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) 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 // Deprecated
func getServiceSliceValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string) []string { func getServiceSliceValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string) []string {
if value, ok := serviceLabels[labelSuffix]; ok { 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) 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 // Deprecated
func getServiceLabelsV1(container dockerData, serviceName string) label.SegmentPropertyValues { func getServiceLabelsV1(container dockerData, serviceName string) label.SegmentPropertyValues {
return label.ExtractServiceProperties(container.Labels)[serviceName] 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) { func TestDockerGetServiceSliceValueV1(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string 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) { func TestDockerCheckPortLabelsV1(t *testing.T) {
testCases := []struct { testCases := []struct {
container docker.ContainerJSON container docker.ContainerJSON

View file

@ -91,7 +91,9 @@ func (p *Provider) filterInstance(i ecsInstance) bool {
} }
func (p *Provider) getFrontendRule(i ecsInstance) string { 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) return label.GetStringValue(i.TraefikLabels, label.TraefikFrontendRule, defaultRule)
} }

View file

@ -10,6 +10,16 @@ import (
const testTaskName = "taskID" 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. // Functions related to building applications.
func withApplications(apps ...marathon.Application) *marathon.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 { 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 { 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) { func (p *Provider) getServerHost(task marathon.Task, app appData) (string, error) {
if app.IPAddressPerTask == nil || p.ForceTaskHostname { 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 return task.Host, nil
} }

View file

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

View file

@ -729,6 +729,16 @@ func TestProviderGetFrontendRule(t *testing.T) {
}, },
expected: "Host:foo.rancher.localhost", 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 /", desc: "host with /",
service: rancherData{ service: rancherData{
@ -746,26 +756,6 @@ func TestProviderGetFrontendRule(t *testing.T) {
}, },
expected: "Host:foo.bar.com", 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 { for _, test := range testCases {
@ -849,6 +839,18 @@ func TestGetServers(t *testing.T) {
}, },
expected: nil, 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", desc: "should use default weight when invalid weight value",
service: rancherData{ service: rancherData{

View file

@ -11,20 +11,20 @@ import (
// NewHeaderRewriter Create a header rewriter // NewHeaderRewriter Create a header rewriter
func NewHeaderRewriter(trustedIPs []string, insecure bool) (forward.ReqRewriter, error) { 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 { if err != nil {
return nil, err return nil, err
} }
h, err := os.Hostname() hostname, err := os.Hostname()
if err != nil { if err != nil {
h = "localhost" hostname = "localhost"
} }
return &headerRewriter{ return &headerRewriter{
secureRewriter: &forward.HeaderRewriter{TrustForwardHeader: true, Hostname: h}, secureRewriter: &forward.HeaderRewriter{TrustForwardHeader: false, Hostname: hostname},
insecureRewriter: &forward.HeaderRewriter{TrustForwardHeader: false, Hostname: h}, insecureRewriter: &forward.HeaderRewriter{TrustForwardHeader: true, Hostname: hostname},
ips: IPs, ips: ips,
insecure: insecure, insecure: insecure,
}, nil }, nil
} }
@ -37,16 +37,17 @@ type headerRewriter struct {
} }
func (h *headerRewriter) Rewrite(req *http.Request) { 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 { if err != nil {
log.Error(err) log.Error(err)
h.secureRewriter.Rewrite(req) h.secureRewriter.Rewrite(req)
return 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 { if !ok {
return false, fmt.Errorf("type error %v", addr) 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) { func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, error) {
config := &tls.Config{} config := &tls.Config{}
domainsCertificates := make(map[string]map[string]*tls.Certificate) domainsCertificates := make(map[string]map[string]*tls.Certificate)
if c.isEmpty() { if c.isEmpty() {
config.Certificates = []tls.Certificate{} config.Certificates = []tls.Certificate{}
cert, err := generate.DefaultCertificate() cert, err := generate.DefaultCertificate()
if err != nil { if err != nil {
return nil, err return nil, err
} }
config.Certificates = append(config.Certificates, *cert) config.Certificates = append(config.Certificates, *cert)
} else { } else {
for _, certificate := range *c { 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) log.Errorf("Unable to add a certificate to the entryPoint %q : %v", entryPointName, err)
continue continue
} }
for _, certDom := range domainsCertificates { for _, certDom := range domainsCertificates {
for _, cert := range map[string]*tls.Certificate(certDom) { for _, cert := range certDom {
config.Certificates = append(config.Certificates, *cert) config.Certificates = append(config.Certificates, *cert)
} }
} }

View file

@ -28,34 +28,21 @@ type AccessLog struct {
Fields *AccessLogFields `json:"fields,omitempty" description:"AccessLogFields" export:"true"` 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 // AccessLogFilters holds filters configuration
type AccessLogFilters struct { type AccessLogFilters struct {
StatusCodes StatusCodes `json:"statusCodes,omitempty" description:"Keep access logs with status codes in the specified range" export:"true"` 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"` 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 // FieldHeaders holds configuration for access log headers
type FieldHeaders struct { type FieldHeaders struct {
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop | redact" export:"true"` 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"` 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 // Set adds strings elem into the the parser
// it splits str on , and ; // it splits str on , and ;
func (s *StatusCodes) Set(str string) error { func (s *StatusCodes) Set(str string) error {
@ -79,6 +66,9 @@ func (s *StatusCodes) SetValue(val interface{}) {
*s = val.(StatusCodes) *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. // 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. // The String method's output will be used in diagnostics.
func (f *FieldNames) String() string { func (f *FieldNames) String() string {
@ -111,6 +101,9 @@ func (f *FieldNames) SetValue(val interface{}) {
*f = val.(FieldNames) *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. // 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. // The String method's output will be used in diagnostics.
func (f *FieldHeaderNames) String() string { func (f *FieldHeaderNames) String() string {
@ -141,6 +134,13 @@ func (f *FieldHeaderNames) SetValue(val interface{}) {
*f = val.(FieldHeaderNames) *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 // Keep check if the field need to be kept or dropped
func (f *AccessLogFields) Keep(field string) bool { func (f *AccessLogFields) Keep(field string) bool {
defaultKeep := true defaultKeep := true
@ -154,17 +154,6 @@ func (f *AccessLogFields) Keep(field string) bool {
return defaultKeep 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 // KeepHeader checks if the headers need to be kept, dropped or redacted and returns the status
func (f *AccessLogFields) KeepHeader(header string) string { func (f *AccessLogFields) KeepHeader(header string) string {
defaultValue := AccessLogKeep defaultValue := AccessLogKeep
@ -178,6 +167,17 @@ func (f *AccessLogFields) KeepHeader(header string) string {
return defaultValue 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 { func checkFieldHeaderValue(value string, defaultValue string) string {
if value == AccessLogKeep || value == AccessLogDrop || value == AccessLogRedact { if value == AccessLogKeep || value == AccessLogDrop || value == AccessLogRedact {
return value return value

View file

@ -1,11 +1,11 @@
package whitelist package whitelist
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"strings"
"github.com/pkg/errors"
) )
const ( 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 // 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 { if ip.insecure {
return true, nil, nil return nil
} }
var invalidMatches []string
if ip.useXForwardedFor { if ip.useXForwardedFor {
xFFs := req.Header[XForwardedFor] xFFs := req.Header[XForwardedFor]
if len(xFFs) > 1 { if len(xFFs) > 0 {
for _, xFF := range xFFs { for _, xFF := range xFFs {
ok, i, err := ip.contains(parseHost(xFF)) ok, err := ip.contains(parseHost(xFF))
if err != nil { if err != nil {
return false, nil, err return err
} }
if ok { if ok {
return ok, i, nil return nil
} }
invalidMatches = append(invalidMatches, xFF)
} }
} }
} }
host, _, err := net.SplitHostPort(req.RemoteAddr) host, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil { 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 // 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) ipAddr, err := parseIP(addr)
if err != nil { 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 ip.ContainsIP(ipAddr), nil
return contains, ipAddr, err
} }
// ContainsIP checks if provided address is in the white list // 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 { if ip.insecure {
return true, nil return true
} }
for _, whiteListIP := range ip.whiteListsIPs { for _, whiteListIP := range ip.whiteListsIPs {
if whiteListIP.Equal(addr) { if whiteListIP.Equal(addr) {
return true, nil return true
} }
} }
for _, whiteListNet := range ip.whiteListsNet { for _, whiteListNet := range ip.whiteListsNet {
if whiteListNet.Contains(addr) { if whiteListNet.Contains(addr) {
return true, nil return true
} }
} }
return false, nil return false
} }
func parseIP(addr string) (net.IP, error) { func parseIP(addr string) (net.IP, error) {

View file

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