Merge branch 'v1.6' into master
This commit is contained in:
commit
3b3ca89483
55 changed files with 844 additions and 569 deletions
|
@ -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",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
104
docs/basics.md
104
docs/basics.md
|
@ -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,7 +452,7 @@ 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 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).
|
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).
|
||||||
|
@ -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:
|
||||||
|
|
|
@ -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`. |
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
|
@ -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 }}
|
||||||
```
|
```
|
||||||
|
|
|
@ -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.
|
|
@ -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`. |
|
||||||
|
|
|
@ -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. |
|
||||||
|
|
|
@ -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`. |
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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/).
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
|
||||||
// empty filters were specified, e.g. by passing --accessLog.filters only (without other filter options)
|
|
||||||
return true
|
|
||||||
case l.httpCodeRanges.Contains(statusCode):
|
|
||||||
return true
|
|
||||||
case l.config.Filters.RetryAttempts == true && retryAttempts > 0:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------------------------
|
if len(l.httpCodeRanges) == 0 && !l.config.Filters.RetryAttempts {
|
||||||
|
// empty filters were specified, e.g. by passing --accessLog.filters only (without other filter options)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"},
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 ""
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
104
server/header_rewriter_test.go
Normal file
104
server/header_rewriter_test.go
Normal 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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue