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",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://localhost:9411/api/v1/spans",
|
||||
|
|
|
@ -327,7 +327,7 @@ func (gc *GlobalConfiguration) initTracing() {
|
|||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
}
|
||||
}
|
||||
if gc.Tracing.Zipkin != nil {
|
||||
|
|
|
@ -138,7 +138,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
|
|||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
Zipkin: nil,
|
||||
},
|
||||
|
@ -151,7 +151,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
|
|||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
},
|
||||
expected: &tracing.Tracing{
|
||||
|
@ -173,7 +173,7 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
|
|||
SamplingServerURL: "http://localhost:5778/sampling",
|
||||
SamplingType: "const",
|
||||
SamplingParam: 1.0,
|
||||
LocalAgentHostPort: "127.0.0.1:6832",
|
||||
LocalAgentHostPort: "127.0.0.1:6831",
|
||||
},
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "http://powpow:9411/api/v1/spans",
|
||||
|
|
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
|
||||
If the custom header name is the same as one header name of the request or response, it will be replaced.
|
||||
|
||||
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, and the `X-Custom-Response-Header` added to the response.
|
||||
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request and the `X-Custom-Response-Header` header added to the response.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
|
@ -276,7 +276,7 @@ In this example, all matches to the path `/cheese` will have the `X-Script-Name`
|
|||
rule = "PathPrefixStrip:/cheese"
|
||||
```
|
||||
|
||||
In this second example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` removed to the request and the `X-Custom-Response-Header` removed to the response.
|
||||
In this second example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` header removed from the request, and the `X-Custom-Response-Header` header removed from the response.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
|
@ -323,12 +323,49 @@ In this example, traffic routed through the first frontend will have the `X-Fram
|
|||
|
||||
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
|
||||
|
||||
#### Servers
|
||||
|
||||
Servers are simply defined using a `url`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
|
||||
|
||||
!!! note
|
||||
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
# ...
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
# ...
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.4:80"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 2
|
||||
```
|
||||
|
||||
- Two backends are defined: `backend1` and `backend2`
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1`.
|
||||
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2`.
|
||||
|
||||
#### Load-balancing
|
||||
|
||||
Various methods of load-balancing are supported:
|
||||
|
||||
- `wrr`: Weighted Round Robin.
|
||||
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others.
|
||||
It also rolls back to original weights if the servers have changed.
|
||||
|
||||
#### Circuit breakers
|
||||
|
||||
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
|
||||
Initial state is Standby. CB observes the statistics and does not modify the request.
|
||||
In case the condition matches, CB enters Tripped state, where it responds with predefined code or redirects to another frontend.
|
||||
|
@ -346,6 +383,26 @@ For example:
|
|||
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
|
||||
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in ranges [500-600) and [0-600).
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
```
|
||||
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
|
||||
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
||||
|
||||
#### Maximum connections
|
||||
|
||||
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can also be applied to each backend.
|
||||
|
||||
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and `maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to evaluate the maximum connections.
|
||||
|
@ -357,13 +414,14 @@ For example:
|
|||
[backends.backend1.maxconn]
|
||||
amount = 10
|
||||
extractorfunc = "request.host"
|
||||
# ...
|
||||
```
|
||||
|
||||
- `backend1` will return `HTTP code 429 Too Many Requests` if there are already 10 requests in progress for the same Host header.
|
||||
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
|
||||
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
|
||||
|
||||
### Sticky sessions
|
||||
#### Sticky sessions
|
||||
|
||||
Sticky sessions are supported with both load balancers.
|
||||
When sticky sessions are enabled, a cookie is set on the initial request.
|
||||
|
@ -371,7 +429,6 @@ The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`).
|
|||
On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy.
|
||||
If not, a new backend will be assigned.
|
||||
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
|
@ -395,7 +452,7 @@ The deprecated way:
|
|||
sticky = true
|
||||
```
|
||||
|
||||
### Health Check
|
||||
#### Health Check
|
||||
|
||||
A health check can be configured in order to remove a backend from LB rotation as long as it keeps returning HTTP status codes other than `200 OK` to HTTP GET requests periodically carried out by Traefik.
|
||||
The check is defined by a 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"
|
||||
```
|
||||
|
||||
### Servers
|
||||
|
||||
Servers are simply defined using a `url`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
|
||||
|
||||
!!! note
|
||||
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
[backends.backend2.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.4:80"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 2
|
||||
```
|
||||
|
||||
- Two backends are defined: `backend1` and `backend2`
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
|
||||
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
|
||||
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Træfik's configuration has two parts:
|
||||
|
|
|
@ -196,6 +196,7 @@ Labels can be used on containers to override default behavior.
|
|||
| Label | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.docker.network` | Set the docker network to use for connections to this container. [1] |
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
|
@ -285,6 +286,7 @@ Segment labels override the default behavior.
|
|||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the segment labels could be used. |
|
||||
| `traefik.<segment_name>.protocol` | Overrides `traefik.protocol`. |
|
||||
| `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |
|
||||
|
|
|
@ -33,6 +33,7 @@ clusters = ["default"]
|
|||
watch = true
|
||||
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label.
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
|
@ -135,6 +136,7 @@ Labels can be used on task containers to override default behaviour:
|
|||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Override the default `port` value. Overrides `NetworkBindings` from Docker Container |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
|
|
|
@ -140,19 +140,20 @@ Træfik can be configured with a file.
|
|||
# ...
|
||||
```
|
||||
|
||||
## Configuration mode
|
||||
## Configuration Mode
|
||||
|
||||
You have three choices:
|
||||
You have two choices:
|
||||
|
||||
- [Simple](/configuration/backends/file/#simple)
|
||||
- [Rules in a Separate File](/configuration/backends/file/#rules-in-a-separate-file)
|
||||
- [Multiple `.toml` Files](/configuration/backends/file/#multiple-toml-files)
|
||||
- [Rules in Træfik configuration file](/configuration/backends/file/#rules-in-trfik-configuration-file)
|
||||
- [Rules in dedicated files](/configuration/backends/file/#rules-in-dedicated-files)
|
||||
|
||||
To enable the file backend, you must either pass the `--file` option to the Træfik binary or put the `[file]` section (with or without inner settings) in the configuration file.
|
||||
|
||||
The configuration file allows managing both backends/frontends and HTTPS certificates (which are not [Let's Encrypt](https://letsencrypt.org) certificates generated through Træfik).
|
||||
|
||||
### Simple
|
||||
TOML templating can be used if rules are not defined in the Træfik configuration file.
|
||||
|
||||
### Rules in Træfik Configuration File
|
||||
|
||||
Add your configuration at the end of the global configuration file `traefik.toml`:
|
||||
|
||||
|
@ -197,9 +198,16 @@ defaultEntryPoints = ["http", "https"]
|
|||
Adding certificates directly to the entryPoint is still maintained but certificates declared in this way cannot be managed dynamically.
|
||||
It's recommended to use the file provider to declare certificates.
|
||||
|
||||
### Rules in a Separate File
|
||||
!!! warning
|
||||
TOML templating cannot be used if rules are defined in the Træfik configuration file.
|
||||
|
||||
Put your rules in a separate file, for example `rules.toml`:
|
||||
### Rules in Dedicated Files
|
||||
|
||||
Træfik allows defining rules in one or more separate files.
|
||||
|
||||
#### One Separate File
|
||||
|
||||
You have to specify the file path in the `file.filename` option.
|
||||
|
||||
```toml
|
||||
# traefik.toml
|
||||
|
@ -213,8 +221,31 @@ defaultEntryPoints = ["http", "https"]
|
|||
|
||||
[file]
|
||||
filename = "rules.toml"
|
||||
watch = true
|
||||
```
|
||||
|
||||
The option `file.watch` allows Træfik to watch file changes automatically.
|
||||
|
||||
#### Multiple Separated Files
|
||||
|
||||
You could have multiple `.toml` files in a directory (and recursively in its sub-directories):
|
||||
|
||||
```toml
|
||||
[file]
|
||||
directory = "/path/to/config/"
|
||||
watch = true
|
||||
```
|
||||
|
||||
The option `file.watch` allows Træfik to watch file changes automatically.
|
||||
|
||||
#### Separate Files Content
|
||||
|
||||
If you are defining rules in one or more separate files, you can use two formats.
|
||||
|
||||
##### Simple Format
|
||||
|
||||
Backends, Frontends and TLS certificates are defined one at time, as described in the file `rules.toml`:
|
||||
|
||||
```toml
|
||||
# rules.toml
|
||||
[backends]
|
||||
|
@ -239,18 +270,34 @@ defaultEntryPoints = ["http", "https"]
|
|||
# ...
|
||||
```
|
||||
|
||||
### Multiple `.toml` Files
|
||||
##### TOML Templating
|
||||
|
||||
You could have multiple `.toml` files in a directory (and recursively in its sub-directories):
|
||||
!!! warning
|
||||
TOML templating can only be used **if rules are defined in one or more separate files**.
|
||||
Templating will not work in the Træfik configuration file.
|
||||
|
||||
Træfik allows using TOML templating.
|
||||
|
||||
Thus, it's possible to define easily lot of Backends, Frontends and TLS certificates as described in the file `template-rules.toml` :
|
||||
|
||||
```toml
|
||||
[file]
|
||||
directory = "/path/to/config/"
|
||||
```
|
||||
# template-rules.toml
|
||||
[backends]
|
||||
{{ range $i, $e := until 100 }}
|
||||
[backends.backend{{ $e }}]
|
||||
#...
|
||||
{{ end }}
|
||||
|
||||
If you want Træfik to watch file changes automatically, just add:
|
||||
[frontends]
|
||||
{{ range $i, $e := until 100 }}
|
||||
[frontends.frontend{{ $e }}]
|
||||
#...
|
||||
{{ end }}
|
||||
|
||||
```toml
|
||||
[file]
|
||||
watch = true
|
||||
|
||||
# HTTPS certificate
|
||||
{{ range $i, $e := until 100 }}
|
||||
[[tls]]
|
||||
#...
|
||||
{{ end }}
|
||||
```
|
||||
|
|
|
@ -239,7 +239,7 @@ The following security annotations are applicable on the Ingress object:
|
|||
|
||||
### Authentication
|
||||
|
||||
Is possible to add additional authentication annotations to the Ingress object.
|
||||
Additional authentication annotations can be added to the Ingress object.
|
||||
The source of the authentication is a Secret object that contains the credentials.
|
||||
|
||||
| Annotation | Description |
|
||||
|
@ -253,3 +253,12 @@ The following limitations hold:
|
|||
|
||||
- The realm is not configurable; the only supported (and default) value is `traefik`.
|
||||
- The Secret must contain a single file only.
|
||||
|
||||
### TLS certificates management
|
||||
|
||||
TLS certificates can be managed in Secrets objects.
|
||||
More information are available in the [User Guide](/user-guide/kubernetes/#add-a-tls-certificate-to-the-ingress).
|
||||
|
||||
!!! note
|
||||
Only TLS certificates provided by users can be stored in Kubernetes Secrets.
|
||||
[Let's Encrypt](https://letsencrypt.org) certificates cannot be managed in Kubernets Secrets yet.
|
|
@ -171,6 +171,7 @@ The following labels can be defined on Marathon applications. They adjust the be
|
|||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.portIndex=1` | Register port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
|
@ -256,6 +257,7 @@ Segment labels override the default behavior.
|
|||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.<segment_name>.portIndex=1` | Create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. |
|
||||
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
|
||||
| `traefik.<segment_name>.protocol=http` | Overrides `traefik.protocol`. |
|
||||
|
|
|
@ -108,6 +108,7 @@ The following labels can be defined on Mesos tasks. They adjust the behavior for
|
|||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.portIndex=1` | Register port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
|
|
|
@ -140,6 +140,7 @@ Labels can be used on task containers to override default behavior:
|
|||
|
||||
| Label | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
|
@ -223,6 +224,7 @@ Segment labels override the default behavior.
|
|||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<segment_name>.domain` | Default domain used for frontend rules. |
|
||||
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the segment labels could be used. |
|
||||
| `traefik.<segment_name>.protocol` | Overrides `traefik.protocol`. |
|
||||
| `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |
|
||||
|
|
|
@ -120,7 +120,7 @@ Compress:true
|
|||
WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16
|
||||
WhiteList.UseXForwardedFor:true
|
||||
ProxyProtocol.TrustedIPs:192.168.0.1
|
||||
ProxyProtocol.Insecure:tue
|
||||
ProxyProtocol.Insecure:true
|
||||
ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24
|
||||
Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0
|
||||
Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e
|
||||
|
|
|
@ -48,11 +48,14 @@ Træfik supports two backends: Jaeger and Zipkin.
|
|||
|
||||
# Local Agent Host Port instructs reporter to send spans to jaeger-agent at this address
|
||||
#
|
||||
# Default: "127.0.0.1:6832"
|
||||
# Default: "127.0.0.1:6831"
|
||||
#
|
||||
localAgentHostPort = "127.0.0.1:6832"
|
||||
localAgentHostPort = "127.0.0.1:6831"
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Træfik is only able to send data over compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent).
|
||||
|
||||
## Zipkin
|
||||
|
||||
```toml
|
||||
|
|
|
@ -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).
|
||||
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!**
|
||||
|
||||
|
@ -164,9 +165,10 @@ IP: 172.27.0.4
|
|||
|
||||
### 4 — Enjoy Træfik's Magic
|
||||
|
||||
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Træfik work for you! Whatever your infrastructure is, there is probably [an available Træfik backend](https://docs.traefik.io/configuration/backends/available) that will do the job.
|
||||
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](/) and let Træfik work for you!
|
||||
Whatever your infrastructure is, there is probably [an available Træfik backend](/#supported-backends) that will do the job.
|
||||
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](/user-guide/docker-and-lets-encrypt/).
|
||||
|
||||
## Resources
|
||||
|
||||
|
|
|
@ -101,6 +101,7 @@ IP: 172.27.0.4
|
|||
|
||||
### 4 — Enjoy Træfik's Magic
|
||||
|
||||
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Træfik work for you! Whatever your infrastructure is, there is probably [an available Træfik backend](https://docs.traefik.io/configuration/backends/available) that will do the job.
|
||||
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Træfik work for you!
|
||||
Whatever your infrastructure is, there is probably [an available Træfik backend](https://docs.traefik.io/#supported-backends) that will do the job.
|
||||
|
||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).
|
|
@ -194,12 +194,14 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
|||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/test2", nil)
|
||||
try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test2.localhost"
|
||||
try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
|
|
|
@ -267,23 +267,26 @@ func (l *LogHandler) redactHeaders(headers http.Header, fields logrus.Fields, pr
|
|||
}
|
||||
|
||||
func (l *LogHandler) keepAccessLog(statusCode, retryAttempts int) bool {
|
||||
switch {
|
||||
case l.config.Filters == nil:
|
||||
if l.config.Filters == nil {
|
||||
// no filters were specified
|
||||
return true
|
||||
case len(l.httpCodeRanges) == 0 && l.config.Filters.RetryAttempts == false:
|
||||
// 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
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
|
|||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
|
||||
if config.TLS != nil {
|
||||
tlsConfig, err := config.TLS.CreateTLSConfig()
|
||||
if err != nil {
|
||||
|
@ -32,10 +33,12 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
|
|||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
httpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
}
|
||||
|
||||
forwardReq, err := http.NewRequest(http.MethodGet, config.Address, nil)
|
||||
tracing.LogRequest(tracing.GetSpan(r), forwardReq)
|
||||
if err != nil {
|
||||
|
@ -68,6 +71,8 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
|
|||
if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices {
|
||||
log.Debugf("Remote error %s. StatusCode: %d", config.Address, forwardResponse.StatusCode)
|
||||
|
||||
utils.CopyHeaders(w.Header(), forwardResponse.Header)
|
||||
|
||||
// Grab the location header, if any.
|
||||
redirectURL, err := forwardResponse.Location()
|
||||
|
||||
|
@ -79,12 +84,7 @@ func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next
|
|||
}
|
||||
} else if redirectURL.String() != "" {
|
||||
// Set the location in our response if one was sent back.
|
||||
w.Header().Add("Location", redirectURL.String())
|
||||
}
|
||||
|
||||
// Pass any Set-Cookie headers the forward auth server provides
|
||||
for _, cookie := range forwardResponse.Cookies() {
|
||||
w.Header().Add("Set-Cookie", cookie.String())
|
||||
w.Header().Set("Location", redirectURL.String())
|
||||
}
|
||||
|
||||
tracing.LogResponseCode(tracing.GetSpan(r), forwardResponse.StatusCode)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/containous/traefik/testhelpers"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
|
@ -110,7 +111,6 @@ func TestForwardAuthRedirect(t *testing.T) {
|
|||
assert.Equal(t, http.StatusFound, res.StatusCode, "they should be equal")
|
||||
|
||||
location, err := res.Location()
|
||||
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, "http://example.com/redirect-test", location.String(), "they should be equal")
|
||||
|
||||
|
@ -119,10 +119,11 @@ func TestForwardAuthRedirect(t *testing.T) {
|
|||
assert.NotEmpty(t, string(body), "there should be something in the body")
|
||||
}
|
||||
|
||||
func TestForwardAuthCookie(t *testing.T) {
|
||||
func TestForwardAuthFailResponseHeaders(t *testing.T) {
|
||||
authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cookie := &http.Cookie{Name: "example", Value: "testing", Path: "/"}
|
||||
http.SetCookie(w, cookie)
|
||||
w.Header().Add("X-Foo", "bar")
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
}))
|
||||
defer authTs.Close()
|
||||
|
@ -142,23 +143,36 @@ func TestForwardAuthCookie(t *testing.T) {
|
|||
ts := httptest.NewServer(n)
|
||||
defer ts.Close()
|
||||
|
||||
client := &http.Client{}
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
||||
client := &http.Client{}
|
||||
res, err := client.Do(req)
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode, "they should be equal")
|
||||
|
||||
require.Len(t, res.Cookies(), 1)
|
||||
for _, cookie := range res.Cookies() {
|
||||
assert.Equal(t, "testing", cookie.Value, "they should be equal")
|
||||
}
|
||||
|
||||
expectedHeaders := http.Header{
|
||||
"Content-Length": []string{"10"},
|
||||
"Content-Type": []string{"text/plain; charset=utf-8"},
|
||||
"X-Foo": []string{"bar"},
|
||||
"Set-Cookie": []string{"example=testing; Path=/"},
|
||||
"X-Content-Type-Options": []string{"nosniff"},
|
||||
}
|
||||
|
||||
assert.Len(t, res.Header, 6)
|
||||
for key, value := range expectedHeaders {
|
||||
assert.Equal(t, value, res.Header[key])
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, "Forbidden\n", string(body), "they should be equal")
|
||||
}
|
||||
|
||||
func Test_writeHeader(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
headers map[string]string
|
||||
|
|
|
@ -3,15 +3,17 @@ package errorpages
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
@ -75,8 +77,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
|||
recorder := newResponseRecorder(w)
|
||||
next.ServeHTTP(recorder, req)
|
||||
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
|
||||
// check the recorder code against the configured http status code ranges
|
||||
for _, block := range h.httpCodeRanges {
|
||||
if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] {
|
||||
|
@ -88,20 +88,43 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
|||
query = strings.Replace(query, "{status}", strconv.Itoa(recorder.GetCode()), -1)
|
||||
}
|
||||
|
||||
if newReq, err := http.NewRequest(http.MethodGet, h.backendURL+query, nil); err != nil {
|
||||
pageReq, err := newRequest(h.backendURL + query)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
w.Write([]byte(http.StatusText(recorder.GetCode())))
|
||||
} else {
|
||||
h.backendHandler.ServeHTTP(w, newReq)
|
||||
return
|
||||
}
|
||||
|
||||
utils.CopyHeaders(pageReq.Header, req.Header)
|
||||
utils.CopyHeaders(w.Header(), recorder.Header())
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
h.backendHandler.ServeHTTP(w, pageReq)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// did not catch a configured status code so proceed with the request
|
||||
utils.CopyHeaders(w.Header(), recorder.Header())
|
||||
w.WriteHeader(recorder.GetCode())
|
||||
w.Write(recorder.GetBody().Bytes())
|
||||
}
|
||||
|
||||
func newRequest(baseURL string) (*http.Request, error) {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error pages: error when parse URL: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error pages: error when create query: %v", err)
|
||||
}
|
||||
|
||||
req.RequestURI = u.RequestURI()
|
||||
return req, nil
|
||||
}
|
||||
|
||||
type responseRecorder interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
|
|
|
@ -65,7 +65,7 @@ func TestHandler(t *testing.T) {
|
|||
errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503-503"}},
|
||||
backendCode: http.StatusServiceUnavailable,
|
||||
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.RequestURI() == "/"+strconv.Itoa(503) {
|
||||
if r.RequestURI == "/503" {
|
||||
fmt.Fprintln(w, "My 503 page.")
|
||||
} else {
|
||||
fmt.Fprintln(w, "Failed")
|
||||
|
@ -82,7 +82,7 @@ func TestHandler(t *testing.T) {
|
|||
errorPage: &types.ErrorPage{Backend: "error", Query: "/{status}", Status: []string{"503"}},
|
||||
backendCode: http.StatusServiceUnavailable,
|
||||
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.RequestURI() == "/"+strconv.Itoa(503) {
|
||||
if r.RequestURI == "/503" {
|
||||
fmt.Fprintln(w, "My 503 page.")
|
||||
} else {
|
||||
fmt.Fprintln(w, "Failed")
|
||||
|
@ -239,7 +239,7 @@ func TestHandlerOldWay(t *testing.T) {
|
|||
|
||||
func TestHandlerOldWayIntegration(t *testing.T) {
|
||||
errorPagesServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.RequestURI() == "/"+strconv.Itoa(503) {
|
||||
if r.URL.RequestURI() == "/503" {
|
||||
fmt.Fprintln(w, "My 503 page.")
|
||||
} else {
|
||||
fmt.Fprintln(w, "Test Server")
|
||||
|
@ -318,6 +318,7 @@ func TestHandlerOldWayIntegration(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Foo", "bar")
|
||||
w.WriteHeader(test.backendCode)
|
||||
fmt.Fprintln(w, http.StatusText(test.backendCode))
|
||||
})
|
||||
|
@ -330,6 +331,7 @@ func TestHandlerOldWayIntegration(t *testing.T) {
|
|||
n.ServeHTTP(recorder, req)
|
||||
|
||||
test.validate(t, recorder)
|
||||
assert.Equal(t, "bar", recorder.Header().Get("X-Foo"), "missing header")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,21 +38,15 @@ func NewIPWhiteLister(whiteList []string, useXForwardedFor bool) (*IPWhiteLister
|
|||
}
|
||||
|
||||
func (wl *IPWhiteLister) handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
allowed, ip, err := wl.whiteLister.IsAuthorized(r)
|
||||
err := wl.whiteLister.IsAuthorized(r)
|
||||
if err != nil {
|
||||
tracing.SetErrorAndDebugLog(r, "request %+v matched none of the white list - rejecting", r)
|
||||
tracing.SetErrorAndDebugLog(r, "request %+v - rejecting: %v", r, err)
|
||||
reject(w)
|
||||
return
|
||||
}
|
||||
|
||||
if allowed {
|
||||
tracing.SetErrorAndDebugLog(r, "request %+v matched white list %s - passing", r, wl.whiteLister)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
tracing.SetErrorAndDebugLog(r, "source-IP %s matched none of the white list - rejecting", ip)
|
||||
reject(w)
|
||||
}
|
||||
|
||||
func (wl *IPWhiteLister) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
|
@ -63,5 +57,8 @@ func reject(w http.ResponseWriter) {
|
|||
statusCode := http.StatusForbidden
|
||||
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write([]byte(http.StatusText(statusCode)))
|
||||
_, err := w.Write([]byte(http.StatusText(statusCode)))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,13 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
|
|||
xForwardedFor: []string{"30.30.30.30", "40.40.40.40"},
|
||||
expected: 200,
|
||||
},
|
||||
{
|
||||
desc: "authorized with only one X-Forwarded-For",
|
||||
whiteList: []string{"30.30.30.30"},
|
||||
useXForwardedFor: true,
|
||||
xForwardedFor: []string{"30.30.30.30"},
|
||||
expected: 200,
|
||||
},
|
||||
{
|
||||
desc: "non authorized with X-Forwarded-For",
|
||||
whiteList: []string{"30.30.30.30"},
|
||||
|
|
|
@ -73,7 +73,10 @@ func (t *Tracing) IsEnabled() bool {
|
|||
// Close tracer
|
||||
func (t *Tracing) Close() {
|
||||
if t.closer != nil {
|
||||
t.closer.Close()
|
||||
err := t.closer.Close()
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,10 +107,13 @@ func GetSpan(r *http.Request) opentracing.Span {
|
|||
// InjectRequestHeaders used to inject OpenTracing headers into the request
|
||||
func InjectRequestHeaders(r *http.Request) {
|
||||
if span := GetSpan(r); span != nil {
|
||||
opentracing.GlobalTracer().Inject(
|
||||
err := opentracing.GlobalTracer().Inject(
|
||||
span.Context(),
|
||||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(r.Header))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ type Account struct {
|
|||
|
||||
const (
|
||||
// RegistrationURLPathV1Regexp is a regexp which match ACME registration URL in the V1 format
|
||||
RegistrationURLPathV1Regexp string = `^.*/acme/reg/\d+$`
|
||||
RegistrationURLPathV1Regexp = `^.*/acme/reg/\d+$`
|
||||
)
|
||||
|
||||
// NewAccount creates an account
|
||||
|
|
|
@ -330,7 +330,6 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
|||
safe.Go(func() {
|
||||
if _, err := p.resolveCertificate(domain, true); err != nil {
|
||||
log.Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
|
||||
} else {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -384,15 +383,6 @@ func (p *Provider) watchCertificate() {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *Provider) deleteCertificateForDomain(domain types.Domain) {
|
||||
for k, cert := range p.certificates {
|
||||
if reflect.DeepEqual(cert.Domain, domain) {
|
||||
p.certificates = append(p.certificates[:k], p.certificates[k+1:]...)
|
||||
}
|
||||
}
|
||||
p.saveCertificates()
|
||||
}
|
||||
|
||||
func (p *Provider) saveCertificates() {
|
||||
err := p.Store.SaveCertificates(p.certificates)
|
||||
if err != nil {
|
||||
|
|
|
@ -237,19 +237,6 @@ func hasTag(name string, tags []string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func hasTagPrefix(name string, tags []string) bool {
|
||||
lowerName := strings.ToLower(name)
|
||||
|
||||
for _, tag := range tags {
|
||||
lowerTag := strings.ToLower(tag)
|
||||
|
||||
if strings.HasPrefix(lowerTag, lowerName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getTag(name string, tags []string, defaultValue string) string {
|
||||
lowerName := strings.ToLower(name)
|
||||
|
||||
|
|
|
@ -156,17 +156,6 @@ func (p *Provider) getFuncSliceAttribute(name string) func(tags []string) []stri
|
|||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getMapAttribute(name string, tags []string) map[string]string {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
||||
if len(rawValue) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return label.ParseMapValue(p.getPrefixedName(name), rawValue)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getFuncIntAttribute(name string, defaultValue int) func(tags []string) int {
|
||||
return func(tags []string) int {
|
||||
|
@ -180,13 +169,6 @@ func (p *Provider) getFuncBoolAttribute(name string, defaultValue bool) func(tag
|
|||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getFuncHasAttributePrefix(name string) func(tags []string) bool {
|
||||
return func(tags []string) bool {
|
||||
return p.hasAttributePrefix(name, tags)
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (p *Provider) getInt64Attribute(name string, tags []string, defaultValue int64) int64 {
|
||||
rawValue := getTag(p.getPrefixedName(name), tags, "")
|
||||
|
@ -244,7 +226,3 @@ func (p *Provider) getBoolAttribute(name string, tags []string, defaultValue boo
|
|||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *Provider) hasAttributePrefix(name string, tags []string) bool {
|
||||
return hasTagPrefix(p.getPrefixedName(name), tags)
|
||||
}
|
||||
|
|
|
@ -182,19 +182,20 @@ func (p *Provider) getFrontendRule(container dockerData, segmentLabels map[strin
|
|||
return value
|
||||
}
|
||||
|
||||
domain := label.GetStringValue(segmentLabels, label.TraefikDomain, p.Domain)
|
||||
|
||||
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
|
||||
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + p.Domain
|
||||
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + domain
|
||||
}
|
||||
|
||||
if len(p.Domain) > 0 {
|
||||
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain
|
||||
if len(domain) > 0 {
|
||||
return "Host:" + getSubDomain(container.ServiceName) + "." + domain
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p Provider) getIPAddress(container dockerData) string {
|
||||
|
||||
if value := label.GetStringValue(container.Labels, labelDockerNetwork, ""); value != "" {
|
||||
networkSettings := container.NetworkSettings
|
||||
if networkSettings.Networks != nil {
|
||||
|
@ -246,6 +247,8 @@ func (p Provider) getIPAddress(container dockerData) string {
|
|||
for _, network := range container.NetworkSettings.Networks {
|
||||
return network.Addr
|
||||
}
|
||||
|
||||
log.Warnf("Unable to find the IP address for the container %q.", container.Name)
|
||||
return ""
|
||||
}
|
||||
|
||||
|
@ -314,12 +317,17 @@ func (p *Provider) getServers(containers []dockerData) map[string]types.Server {
|
|||
var servers map[string]types.Server
|
||||
|
||||
for i, container := range containers {
|
||||
ip := p.getIPAddress(container)
|
||||
if len(ip) == 0 {
|
||||
log.Warnf("Unable to find the IP address for the container %q: the server is ignored.", container.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if servers == nil {
|
||||
servers = make(map[string]types.Server)
|
||||
}
|
||||
|
||||
protocol := label.GetStringValue(container.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
|
||||
ip := p.getIPAddress(container)
|
||||
port := getPort(container)
|
||||
|
||||
serverName := "server-" + container.SegmentName + "-" + container.Name
|
||||
|
|
|
@ -406,6 +406,7 @@ func TestDockerBuildConfiguration(t *testing.T) {
|
|||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var dockerDataList []dockerData
|
||||
for _, cont := range test.containers {
|
||||
dData := parseContainer(cont)
|
||||
|
@ -809,15 +810,19 @@ func TestDockerGetFrontendRule(t *testing.T) {
|
|||
expected: "Host:foo.docker.localhost",
|
||||
},
|
||||
{
|
||||
container: containerJSON(name("bar")),
|
||||
expected: "Host:bar.docker.localhost",
|
||||
container: containerJSON(name("foo"),
|
||||
labels(map[string]string{
|
||||
label.TraefikDomain: "traefik.localhost",
|
||||
})),
|
||||
expected: "Host:foo.traefik.localhost",
|
||||
},
|
||||
{
|
||||
container: containerJSON(labels(map[string]string{
|
||||
label.TraefikFrontendRule: "Host:foo.bar",
|
||||
})),
|
||||
expected: "Host:foo.bar",
|
||||
}, {
|
||||
},
|
||||
{
|
||||
container: containerJSON(labels(map[string]string{
|
||||
"com.docker.compose.project": "foo",
|
||||
"com.docker.compose.service": "bar",
|
||||
|
@ -1022,3 +1027,122 @@ func TestDockerGetPort(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetServers(t *testing.T) {
|
||||
p := &Provider{}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
containers []docker.ContainerJSON
|
||||
expected map[string]types.Server
|
||||
}{
|
||||
{
|
||||
desc: "no container",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "with a simple container",
|
||||
containers: []docker.ContainerJSON{
|
||||
containerJSON(
|
||||
name("test1"),
|
||||
withNetwork("testnet", ipv4("10.10.10.10")),
|
||||
ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
})),
|
||||
},
|
||||
expected: map[string]types.Server{
|
||||
"server-test1": {
|
||||
URL: "http://10.10.10.10:80",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with several containers",
|
||||
containers: []docker.ContainerJSON{
|
||||
containerJSON(
|
||||
name("test1"),
|
||||
withNetwork("testnet", ipv4("10.10.10.11")),
|
||||
ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
})),
|
||||
containerJSON(
|
||||
name("test2"),
|
||||
withNetwork("testnet", ipv4("10.10.10.12")),
|
||||
ports(nat.PortMap{
|
||||
"81/tcp": {},
|
||||
})),
|
||||
containerJSON(
|
||||
name("test3"),
|
||||
withNetwork("testnet", ipv4("10.10.10.13")),
|
||||
ports(nat.PortMap{
|
||||
"82/tcp": {},
|
||||
})),
|
||||
},
|
||||
expected: map[string]types.Server{
|
||||
"server-test1": {
|
||||
URL: "http://10.10.10.11:80",
|
||||
Weight: 1,
|
||||
},
|
||||
"server-test2": {
|
||||
URL: "http://10.10.10.12:81",
|
||||
Weight: 1,
|
||||
},
|
||||
"server-test3": {
|
||||
URL: "http://10.10.10.13:82",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "ignore one container because no ip address",
|
||||
containers: []docker.ContainerJSON{
|
||||
containerJSON(
|
||||
name("test1"),
|
||||
withNetwork("testnet", ipv4("")),
|
||||
ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
})),
|
||||
containerJSON(
|
||||
name("test2"),
|
||||
withNetwork("testnet", ipv4("10.10.10.12")),
|
||||
ports(nat.PortMap{
|
||||
"81/tcp": {},
|
||||
})),
|
||||
containerJSON(
|
||||
name("test3"),
|
||||
withNetwork("testnet", ipv4("10.10.10.13")),
|
||||
ports(nat.PortMap{
|
||||
"82/tcp": {},
|
||||
})),
|
||||
},
|
||||
expected: map[string]types.Server{
|
||||
"server-test2": {
|
||||
URL: "http://10.10.10.12:81",
|
||||
Weight: 1,
|
||||
},
|
||||
"server-test3": {
|
||||
URL: "http://10.10.10.13:82",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var dockerDataList []dockerData
|
||||
for _, cont := range test.containers {
|
||||
dData := parseContainer(cont)
|
||||
dockerDataList = append(dockerDataList, dData)
|
||||
}
|
||||
|
||||
servers := p.getServers(dockerDataList)
|
||||
|
||||
assert.Equal(t, test.expected, servers)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -561,8 +561,11 @@ func TestSwarmGetFrontendRule(t *testing.T) {
|
|||
networks: map[string]*docker.NetworkResource{},
|
||||
},
|
||||
{
|
||||
service: swarmService(serviceName("bar")),
|
||||
expected: "Host:bar.docker.localhost",
|
||||
service: swarmService(serviceName("foo"),
|
||||
serviceLabels(map[string]string{
|
||||
label.TraefikDomain: "traefik.localhost",
|
||||
})),
|
||||
expected: "Host:foo.traefik.localhost",
|
||||
networks: map[string]*docker.NetworkResource{},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -27,12 +27,14 @@ func (p Provider) getFrontendRuleV1(container dockerData) string {
|
|||
return value
|
||||
}
|
||||
|
||||
domain := label.GetStringValue(container.Labels, label.TraefikDomain, p.Domain)
|
||||
|
||||
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
|
||||
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + p.Domain
|
||||
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + domain
|
||||
}
|
||||
|
||||
if len(p.Domain) > 0 {
|
||||
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain
|
||||
if len(domain) > 0 {
|
||||
return "Host:" + getSubDomain(container.ServiceName) + "." + domain
|
||||
}
|
||||
|
||||
return ""
|
||||
|
|
|
@ -752,15 +752,19 @@ func TestDockerGetFrontendRuleV1(t *testing.T) {
|
|||
expected: "Host:foo.docker.localhost",
|
||||
},
|
||||
{
|
||||
container: containerJSON(name("bar")),
|
||||
expected: "Host:bar.docker.localhost",
|
||||
container: containerJSON(name("foo"),
|
||||
labels(map[string]string{
|
||||
label.TraefikDomain: "traefik.localhost",
|
||||
})),
|
||||
expected: "Host:foo.traefik.localhost",
|
||||
},
|
||||
{
|
||||
container: containerJSON(labels(map[string]string{
|
||||
label.TraefikFrontendRule: "Host:foo.bar",
|
||||
})),
|
||||
expected: "Host:foo.bar",
|
||||
}, {
|
||||
},
|
||||
{
|
||||
container: containerJSON(labels(map[string]string{
|
||||
"com.docker.compose.project": "foo",
|
||||
"com.docker.compose.service": "bar",
|
||||
|
|
|
@ -527,8 +527,11 @@ func TestSwarmGetFrontendRuleV1(t *testing.T) {
|
|||
networks: map[string]*docker.NetworkResource{},
|
||||
},
|
||||
{
|
||||
service: swarmService(serviceName("bar")),
|
||||
expected: "Host:bar.docker.localhost",
|
||||
service: swarmService(serviceName("foo"),
|
||||
serviceLabels(map[string]string{
|
||||
label.TraefikDomain: "traefik.localhost",
|
||||
})),
|
||||
expected: "Host:foo.traefik.localhost",
|
||||
networks: map[string]*docker.NetworkResource{},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -136,12 +136,6 @@ func getFuncServiceIntLabelV1(labelSuffix string, defaultValue int) func(contain
|
|||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func hasStrictServiceLabelV1(serviceLabels map[string]string, labelSuffix string) bool {
|
||||
value, ok := serviceLabels[labelSuffix]
|
||||
return ok && len(value) > 0
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func getServiceStringValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue string) string {
|
||||
if value, ok := serviceLabels[labelSuffix]; ok {
|
||||
|
@ -150,23 +144,6 @@ func getServiceStringValueV1(container dockerData, serviceLabels map[string]stri
|
|||
return label.GetStringValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func getStrictServiceStringValueV1(serviceLabels map[string]string, labelSuffix string, defaultValue string) string {
|
||||
if value, ok := serviceLabels[labelSuffix]; ok {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func getServiceMapValueV1(container dockerData, serviceLabels map[string]string, serviceName string, labelSuffix string) map[string]string {
|
||||
if value, ok := serviceLabels[labelSuffix]; ok {
|
||||
lblName := label.GetServiceLabel(labelSuffix, serviceName)
|
||||
return label.ParseMapValue(lblName, value)
|
||||
}
|
||||
return label.GetMapValue(container.Labels, label.Prefix+labelSuffix)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func getServiceSliceValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string) []string {
|
||||
if value, ok := serviceLabels[labelSuffix]; ok {
|
||||
|
@ -197,17 +174,6 @@ func getServiceIntLabelV1(container dockerData, serviceName string, labelSuffix
|
|||
return label.GetIntValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func getServiceInt64ValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue int64) int64 {
|
||||
if rawValue, ok := serviceLabels[labelSuffix]; ok {
|
||||
value, err := strconv.ParseInt(rawValue, 10, 64)
|
||||
if err == nil {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return label.GetInt64Value(container.Labels, label.Prefix+labelSuffix, defaultValue)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func getServiceLabelsV1(container dockerData, serviceName string) label.SegmentPropertyValues {
|
||||
return label.ExtractServiceProperties(container.Labels)[serviceName]
|
||||
|
|
|
@ -405,154 +405,6 @@ func TestDockerGetServiceStringValueV1(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDockerHasStrictServiceLabelV1(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
serviceLabels map[string]string
|
||||
labelSuffix string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
desc: "should return false when service don't have label",
|
||||
serviceLabels: map[string]string{},
|
||||
labelSuffix: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "should return true when service have label",
|
||||
serviceLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
labelSuffix: "foo",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := hasStrictServiceLabelV1(test.serviceLabels, test.labelSuffix)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetStrictServiceStringValueV1(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
serviceLabels map[string]string
|
||||
labelSuffix string
|
||||
defaultValue string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "should return a string when the label exists",
|
||||
serviceLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
labelSuffix: "foo",
|
||||
expected: "bar",
|
||||
},
|
||||
{
|
||||
desc: "should return a string when the label exists and value empty",
|
||||
serviceLabels: map[string]string{
|
||||
"foo": "",
|
||||
},
|
||||
labelSuffix: "foo",
|
||||
defaultValue: "cube",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
desc: "should return the default value when the label doesn't exist",
|
||||
serviceLabels: map[string]string{},
|
||||
labelSuffix: "foo",
|
||||
defaultValue: "cube",
|
||||
expected: "cube",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := getStrictServiceStringValueV1(test.serviceLabels, test.labelSuffix, test.defaultValue)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetServiceMapValueV1(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
container docker.ContainerJSON
|
||||
serviceLabels map[string]string
|
||||
serviceName string
|
||||
labelSuffix string
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
desc: "should return when no labels",
|
||||
container: containerJSON(
|
||||
name("test1"),
|
||||
labels(map[string]string{})),
|
||||
serviceLabels: map[string]string{},
|
||||
serviceName: "soo",
|
||||
labelSuffix: "foo",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "should return a map when label exists",
|
||||
container: containerJSON(
|
||||
name("test1"),
|
||||
labels(map[string]string{
|
||||
"traefik.foo": "bir:fii",
|
||||
})),
|
||||
serviceLabels: map[string]string{
|
||||
"foo": "bar:foo",
|
||||
},
|
||||
serviceName: "soo",
|
||||
labelSuffix: "foo",
|
||||
expected: map[string]string{
|
||||
"Bar": "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return a map when label exists (fallback to container labels)",
|
||||
container: containerJSON(
|
||||
name("test1"),
|
||||
labels(map[string]string{
|
||||
"traefik.foo": "bir:fii",
|
||||
})),
|
||||
serviceLabels: map[string]string{
|
||||
"fo": "bar:foo",
|
||||
},
|
||||
serviceName: "soo",
|
||||
labelSuffix: "foo",
|
||||
expected: map[string]string{
|
||||
"Bir": "fii",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dData := parseContainer(test.container)
|
||||
|
||||
actual := getServiceMapValueV1(dData, test.serviceLabels, test.serviceName, test.labelSuffix)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetServiceSliceValueV1(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
@ -672,67 +524,6 @@ func TestDockerGetServiceBoolValueV1(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDockerGetServiceInt64ValueV1(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
container docker.ContainerJSON
|
||||
serviceLabels map[string]string
|
||||
labelSuffix string
|
||||
defaultValue int64
|
||||
expected int64
|
||||
}{
|
||||
{
|
||||
desc: "should return default value when no label",
|
||||
container: containerJSON(
|
||||
name("test1"),
|
||||
labels(map[string]string{})),
|
||||
serviceLabels: map[string]string{},
|
||||
labelSuffix: "foo",
|
||||
defaultValue: 666,
|
||||
expected: 666,
|
||||
},
|
||||
{
|
||||
desc: "should return a int64 when label",
|
||||
container: containerJSON(
|
||||
name("test1"),
|
||||
labels(map[string]string{
|
||||
"traefik.foo": "20",
|
||||
})),
|
||||
serviceLabels: map[string]string{
|
||||
"foo": "10",
|
||||
},
|
||||
labelSuffix: "foo",
|
||||
expected: 10,
|
||||
},
|
||||
{
|
||||
desc: "should return a int64 when label (fallback to container labels)",
|
||||
container: containerJSON(
|
||||
name("test1"),
|
||||
labels(map[string]string{
|
||||
"traefik.foo": "20",
|
||||
})),
|
||||
serviceLabels: map[string]string{
|
||||
"fo": "10",
|
||||
},
|
||||
labelSuffix: "foo",
|
||||
expected: 20,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dData := parseContainer(test.container)
|
||||
|
||||
actual := getServiceInt64ValueV1(dData, test.serviceLabels, test.labelSuffix, test.defaultValue)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerCheckPortLabelsV1(t *testing.T) {
|
||||
testCases := []struct {
|
||||
container docker.ContainerJSON
|
||||
|
|
|
@ -91,7 +91,9 @@ func (p *Provider) filterInstance(i ecsInstance) bool {
|
|||
}
|
||||
|
||||
func (p *Provider) getFrontendRule(i ecsInstance) string {
|
||||
defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + p.Domain
|
||||
domain := label.GetStringValue(i.TraefikLabels, label.TraefikDomain, p.Domain)
|
||||
defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + domain
|
||||
|
||||
return label.GetStringValue(i.TraefikLabels, label.TraefikFrontendRule, defaultRule)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,16 @@ import (
|
|||
|
||||
const testTaskName = "taskID"
|
||||
|
||||
func withAppData(app marathon.Application, segmentName string) appData {
|
||||
segmentProperties := label.ExtractTraefikLabels(stringValueMap(app.Labels))
|
||||
return appData{
|
||||
Application: app,
|
||||
SegmentLabels: segmentProperties[segmentName],
|
||||
SegmentName: segmentName,
|
||||
LinkedApps: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Functions related to building applications.
|
||||
|
||||
func withApplications(apps ...marathon.Application) *marathon.Applications {
|
||||
|
|
|
@ -210,10 +210,12 @@ func (p *Provider) getFrontendRule(app appData) string {
|
|||
}
|
||||
}
|
||||
|
||||
domain := label.GetStringValue(app.SegmentLabels, label.TraefikDomain, p.Domain)
|
||||
|
||||
if len(app.SegmentName) > 0 {
|
||||
return "Host:" + strings.ToLower(provider.Normalize(app.SegmentName)) + "." + p.getSubDomain(app.ID) + "." + p.Domain
|
||||
return "Host:" + strings.ToLower(provider.Normalize(app.SegmentName)) + "." + p.getSubDomain(app.ID) + "." + domain
|
||||
}
|
||||
return "Host:" + p.getSubDomain(app.ID) + "." + p.Domain
|
||||
return "Host:" + p.getSubDomain(app.ID) + "." + domain
|
||||
}
|
||||
|
||||
func getPort(task marathon.Task, app appData) string {
|
||||
|
@ -345,6 +347,9 @@ func (p *Provider) getServer(app appData, task marathon.Task) (string, *types.Se
|
|||
|
||||
func (p *Provider) getServerHost(task marathon.Task, app appData) (string, error) {
|
||||
if app.IPAddressPerTask == nil || p.ForceTaskHostname {
|
||||
if len(task.Host) == 0 {
|
||||
return "", fmt.Errorf("host is undefined for task %q app %q", task.ID, app.ID)
|
||||
}
|
||||
return task.Host, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1034,7 +1034,7 @@ func TestGetPort(t *testing.T) {
|
|||
desc string
|
||||
application marathon.Application
|
||||
task marathon.Task
|
||||
serviceName string
|
||||
segmentName string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
|
@ -1116,23 +1116,23 @@ func TestGetPort(t *testing.T) {
|
|||
},
|
||||
{
|
||||
desc: "multiple task ports with service index available",
|
||||
application: application(withLabel(label.Prefix+"http.portIndex", "0")),
|
||||
application: application(withSegmentLabel(label.TraefikPortIndex, "0", "http")),
|
||||
task: task(taskPorts(80, 443)),
|
||||
serviceName: "http",
|
||||
segmentName: "http",
|
||||
expected: "80",
|
||||
},
|
||||
{
|
||||
desc: "multiple task ports with service port available",
|
||||
application: application(withLabel(label.Prefix+"https.port", "443")),
|
||||
application: application(withSegmentLabel(label.TraefikPort, "443", "https")),
|
||||
task: task(taskPorts(80, 443)),
|
||||
serviceName: "https",
|
||||
segmentName: "https",
|
||||
expected: "443",
|
||||
},
|
||||
{
|
||||
desc: "multiple task ports with services but default port available",
|
||||
application: application(withLabel(label.Prefix+"http.weight", "100")),
|
||||
application: application(withSegmentLabel(label.TraefikWeight, "100", "http")),
|
||||
task: task(taskPorts(80, 443)),
|
||||
serviceName: "http",
|
||||
segmentName: "http",
|
||||
expected: "80",
|
||||
},
|
||||
}
|
||||
|
@ -1142,7 +1142,7 @@ func TestGetPort(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := getPortV1(test.task, test.application, test.serviceName)
|
||||
actual := getPort(test.task, withAppData(test.application, test.segmentName))
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
|
@ -1153,7 +1153,7 @@ func TestGetFrontendRule(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
application marathon.Application
|
||||
serviceName string
|
||||
segmentName string
|
||||
expected string
|
||||
marathonLBCompatibility bool
|
||||
}{
|
||||
|
@ -1163,6 +1163,15 @@ func TestGetFrontendRule(t *testing.T) {
|
|||
marathonLBCompatibility: true,
|
||||
expected: "Host:test.marathon.localhost",
|
||||
},
|
||||
{
|
||||
desc: "label domain",
|
||||
application: application(
|
||||
appID("test"),
|
||||
withLabel(label.TraefikDomain, "traefik.localhost"),
|
||||
),
|
||||
marathonLBCompatibility: true,
|
||||
expected: "Host:test.traefik.localhost",
|
||||
},
|
||||
{
|
||||
desc: "HAProxy vhost available and LB compat disabled",
|
||||
application: application(
|
||||
|
@ -1180,7 +1189,6 @@ func TestGetFrontendRule(t *testing.T) {
|
|||
},
|
||||
{
|
||||
desc: "frontend rule available",
|
||||
|
||||
application: application(
|
||||
withLabel(label.TraefikFrontendRule, "Host:foo.bar"),
|
||||
withLabel("HAPROXY_0_VHOST", "unused"),
|
||||
|
@ -1189,9 +1197,9 @@ func TestGetFrontendRule(t *testing.T) {
|
|||
expected: "Host:foo.bar",
|
||||
},
|
||||
{
|
||||
desc: "service label existing",
|
||||
desc: "segment label frontend rule",
|
||||
application: application(withSegmentLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
|
||||
serviceName: "app",
|
||||
segmentName: "app",
|
||||
marathonLBCompatibility: true,
|
||||
expected: "Host:foo.bar",
|
||||
},
|
||||
|
@ -1206,7 +1214,7 @@ func TestGetFrontendRule(t *testing.T) {
|
|||
MarathonLBCompatibility: test.marathonLBCompatibility,
|
||||
}
|
||||
|
||||
actual := p.getFrontendRuleV1(test.application, test.serviceName)
|
||||
actual := p.getFrontendRule(withAppData(test.application, test.segmentName))
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
|
@ -1217,7 +1225,7 @@ func TestGetBackendName(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
application marathon.Application
|
||||
serviceName string
|
||||
segmentName string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
|
@ -1231,9 +1239,9 @@ func TestGetBackendName(t *testing.T) {
|
|||
expected: "backendbar",
|
||||
},
|
||||
{
|
||||
desc: "service label existing",
|
||||
desc: "segment label existing",
|
||||
application: application(withSegmentLabel(label.TraefikBackend, "bar", "app")),
|
||||
serviceName: "app",
|
||||
segmentName: "app",
|
||||
expected: "backendbar",
|
||||
},
|
||||
}
|
||||
|
@ -1245,7 +1253,7 @@ func TestGetBackendName(t *testing.T) {
|
|||
|
||||
p := &Provider{}
|
||||
|
||||
actual := p.getBackendNameV1(test.application, test.serviceName)
|
||||
actual := p.getBackendName(withAppData(test.application, test.segmentName))
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
|
@ -1256,7 +1264,7 @@ func TestGetServers(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
application marathon.Application
|
||||
serviceName string
|
||||
segmentName string
|
||||
expected map[string]types.Server
|
||||
}{
|
||||
{
|
||||
|
@ -1304,12 +1312,14 @@ func TestGetServers(t *testing.T) {
|
|||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
if test.desc == "should return nil when all hosts are empty" {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := p.getServersV1(test.application, test.serviceName)
|
||||
actual := p.getServers(withAppData(test.application, test.segmentName))
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,10 +138,11 @@ func (p *Provider) getFrontendRuleV1(application marathon.Application, serviceNa
|
|||
}
|
||||
}
|
||||
|
||||
domain := label.GetStringValue(labels, label.SuffixDomain, p.Domain)
|
||||
if len(serviceName) > 0 {
|
||||
return "Host:" + strings.ToLower(provider.Normalize(serviceName)) + "." + p.getSubDomain(application.ID) + "." + p.Domain
|
||||
return "Host:" + strings.ToLower(provider.Normalize(serviceName)) + "." + p.getSubDomain(application.ID) + "." + domain
|
||||
}
|
||||
return "Host:" + p.getSubDomain(application.ID) + "." + p.Domain
|
||||
return "Host:" + p.getSubDomain(application.ID) + "." + domain
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
|
|
|
@ -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 {
|
||||
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 {
|
||||
|
|
|
@ -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 {
|
||||
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
|
||||
|
|
|
@ -124,7 +124,9 @@ func (p *Provider) serviceFilter(service rancherData) bool {
|
|||
}
|
||||
|
||||
func (p *Provider) getFrontendRule(serviceName string, labels map[string]string) string {
|
||||
defaultRule := "Host:" + strings.ToLower(strings.Replace(serviceName, "/", ".", -1)) + "." + p.Domain
|
||||
domain := label.GetStringValue(labels, label.TraefikDomain, p.Domain)
|
||||
defaultRule := "Host:" + strings.ToLower(strings.Replace(serviceName, "/", ".", -1)) + "." + domain
|
||||
|
||||
return label.GetStringValue(labels, label.TraefikFrontendRule, defaultRule)
|
||||
}
|
||||
|
||||
|
@ -164,6 +166,11 @@ func getServers(service rancherData) map[string]types.Server {
|
|||
var servers map[string]types.Server
|
||||
|
||||
for index, ip := range service.Containers {
|
||||
if len(ip) == 0 {
|
||||
log.Warnf("Unable to find the IP address for a container in the service %q: this container is ignored.", service.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if servers == nil {
|
||||
servers = make(map[string]types.Server)
|
||||
}
|
||||
|
|
|
@ -729,6 +729,16 @@ func TestProviderGetFrontendRule(t *testing.T) {
|
|||
},
|
||||
expected: "Host:foo.rancher.localhost",
|
||||
},
|
||||
{
|
||||
desc: "with domain label",
|
||||
service: rancherData{
|
||||
Name: "test-service",
|
||||
Labels: map[string]string{
|
||||
label.TraefikDomain: "traefik.localhost",
|
||||
},
|
||||
},
|
||||
expected: "Host:test-service.traefik.localhost",
|
||||
},
|
||||
{
|
||||
desc: "host with /",
|
||||
service: rancherData{
|
||||
|
@ -746,26 +756,6 @@ func TestProviderGetFrontendRule(t *testing.T) {
|
|||
},
|
||||
expected: "Host:foo.bar.com",
|
||||
},
|
||||
{
|
||||
desc: "with Path label",
|
||||
service: rancherData{
|
||||
Name: "test-service",
|
||||
Labels: map[string]string{
|
||||
label.TraefikFrontendRule: "Path:/test",
|
||||
},
|
||||
},
|
||||
expected: "Path:/test",
|
||||
},
|
||||
{
|
||||
desc: "with PathPrefix label",
|
||||
service: rancherData{
|
||||
Name: "test-service",
|
||||
Labels: map[string]string{
|
||||
label.TraefikFrontendRule: "PathPrefix:/test2",
|
||||
},
|
||||
},
|
||||
expected: "PathPrefix:/test2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -849,6 +839,18 @@ func TestGetServers(t *testing.T) {
|
|||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "should return nil when no server IPs",
|
||||
service: rancherData{
|
||||
Labels: map[string]string{
|
||||
label.TraefikWeight: "7",
|
||||
},
|
||||
Containers: []string{""},
|
||||
Health: "healthy",
|
||||
State: "active",
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "should use default weight when invalid weight value",
|
||||
service: rancherData{
|
||||
|
|
|
@ -11,20 +11,20 @@ import (
|
|||
|
||||
// NewHeaderRewriter Create a header rewriter
|
||||
func NewHeaderRewriter(trustedIPs []string, insecure bool) (forward.ReqRewriter, error) {
|
||||
IPs, err := whitelist.NewIP(trustedIPs, insecure, true)
|
||||
ips, err := whitelist.NewIP(trustedIPs, insecure, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := os.Hostname()
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
h = "localhost"
|
||||
hostname = "localhost"
|
||||
}
|
||||
|
||||
return &headerRewriter{
|
||||
secureRewriter: &forward.HeaderRewriter{TrustForwardHeader: true, Hostname: h},
|
||||
insecureRewriter: &forward.HeaderRewriter{TrustForwardHeader: false, Hostname: h},
|
||||
ips: IPs,
|
||||
secureRewriter: &forward.HeaderRewriter{TrustForwardHeader: false, Hostname: hostname},
|
||||
insecureRewriter: &forward.HeaderRewriter{TrustForwardHeader: true, Hostname: hostname},
|
||||
ips: ips,
|
||||
insecure: insecure,
|
||||
}, nil
|
||||
}
|
||||
|
@ -37,16 +37,17 @@ type headerRewriter struct {
|
|||
}
|
||||
|
||||
func (h *headerRewriter) Rewrite(req *http.Request) {
|
||||
authorized, _, err := h.ips.IsAuthorized(req)
|
||||
if h.insecure {
|
||||
h.insecureRewriter.Rewrite(req)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.ips.IsAuthorized(req)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
h.secureRewriter.Rewrite(req)
|
||||
return
|
||||
}
|
||||
|
||||
if h.insecure || authorized {
|
||||
h.secureRewriter.Rewrite(req)
|
||||
} else {
|
||||
h.insecureRewriter.Rewrite(req)
|
||||
}
|
||||
}
|
||||
|
|
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 {
|
||||
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) {
|
||||
config := &tls.Config{}
|
||||
domainsCertificates := make(map[string]map[string]*tls.Certificate)
|
||||
|
||||
if c.isEmpty() {
|
||||
config.Certificates = []tls.Certificate{}
|
||||
|
||||
cert, err := generate.DefaultCertificate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.Certificates = append(config.Certificates, *cert)
|
||||
} else {
|
||||
for _, certificate := range *c {
|
||||
|
@ -104,8 +107,9 @@ func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, erro
|
|||
log.Errorf("Unable to add a certificate to the entryPoint %q : %v", entryPointName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, certDom := range domainsCertificates {
|
||||
for _, cert := range map[string]*tls.Certificate(certDom) {
|
||||
for _, cert := range certDom {
|
||||
config.Certificates = append(config.Certificates, *cert)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,34 +28,21 @@ type AccessLog struct {
|
|||
Fields *AccessLogFields `json:"fields,omitempty" description:"AccessLogFields" export:"true"`
|
||||
}
|
||||
|
||||
// StatusCodes holds status codes ranges to filter access log
|
||||
type StatusCodes []string
|
||||
|
||||
// AccessLogFilters holds filters configuration
|
||||
type AccessLogFilters struct {
|
||||
StatusCodes StatusCodes `json:"statusCodes,omitempty" description:"Keep access logs with status codes in the specified range" export:"true"`
|
||||
RetryAttempts bool `json:"retryAttempts,omitempty" description:"Keep access logs when at least one retry happened" export:"true"`
|
||||
}
|
||||
|
||||
// FieldNames holds maps of fields with specific mode
|
||||
type FieldNames map[string]string
|
||||
|
||||
// AccessLogFields holds configuration for access log fields
|
||||
type AccessLogFields struct {
|
||||
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop" export:"true"`
|
||||
Names FieldNames `json:"names,omitempty" description:"Override mode for fields" export:"true"`
|
||||
Headers *FieldHeaders `json:"headers,omitempty" description:"Headers to keep, drop or redact" export:"true"`
|
||||
}
|
||||
|
||||
// FieldHeaderNames holds maps of fields with specific mode
|
||||
type FieldHeaderNames map[string]string
|
||||
|
||||
// FieldHeaders holds configuration for access log headers
|
||||
type FieldHeaders struct {
|
||||
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop | redact" export:"true"`
|
||||
Names FieldHeaderNames `json:"names,omitempty" description:"Override mode for headers" export:"true"`
|
||||
}
|
||||
|
||||
// StatusCodes holds status codes ranges to filter access log
|
||||
type StatusCodes []string
|
||||
|
||||
// Set adds strings elem into the the parser
|
||||
// it splits str on , and ;
|
||||
func (s *StatusCodes) Set(str string) error {
|
||||
|
@ -79,6 +66,9 @@ func (s *StatusCodes) SetValue(val interface{}) {
|
|||
*s = val.(StatusCodes)
|
||||
}
|
||||
|
||||
// FieldNames holds maps of fields with specific mode
|
||||
type FieldNames map[string]string
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (f *FieldNames) String() string {
|
||||
|
@ -111,6 +101,9 @@ func (f *FieldNames) SetValue(val interface{}) {
|
|||
*f = val.(FieldNames)
|
||||
}
|
||||
|
||||
// FieldHeaderNames holds maps of fields with specific mode
|
||||
type FieldHeaderNames map[string]string
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (f *FieldHeaderNames) String() string {
|
||||
|
@ -141,6 +134,13 @@ func (f *FieldHeaderNames) SetValue(val interface{}) {
|
|||
*f = val.(FieldHeaderNames)
|
||||
}
|
||||
|
||||
// AccessLogFields holds configuration for access log fields
|
||||
type AccessLogFields struct {
|
||||
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop" export:"true"`
|
||||
Names FieldNames `json:"names,omitempty" description:"Override mode for fields" export:"true"`
|
||||
Headers *FieldHeaders `json:"headers,omitempty" description:"Headers to keep, drop or redact" export:"true"`
|
||||
}
|
||||
|
||||
// Keep check if the field need to be kept or dropped
|
||||
func (f *AccessLogFields) Keep(field string) bool {
|
||||
defaultKeep := true
|
||||
|
@ -154,17 +154,6 @@ func (f *AccessLogFields) Keep(field string) bool {
|
|||
return defaultKeep
|
||||
}
|
||||
|
||||
func checkFieldValue(value string, defaultKeep bool) bool {
|
||||
switch value {
|
||||
case AccessLogKeep:
|
||||
return true
|
||||
case AccessLogDrop:
|
||||
return false
|
||||
default:
|
||||
return defaultKeep
|
||||
}
|
||||
}
|
||||
|
||||
// KeepHeader checks if the headers need to be kept, dropped or redacted and returns the status
|
||||
func (f *AccessLogFields) KeepHeader(header string) string {
|
||||
defaultValue := AccessLogKeep
|
||||
|
@ -178,6 +167,17 @@ func (f *AccessLogFields) KeepHeader(header string) string {
|
|||
return defaultValue
|
||||
}
|
||||
|
||||
func checkFieldValue(value string, defaultKeep bool) bool {
|
||||
switch value {
|
||||
case AccessLogKeep:
|
||||
return true
|
||||
case AccessLogDrop:
|
||||
return false
|
||||
default:
|
||||
return defaultKeep
|
||||
}
|
||||
}
|
||||
|
||||
func checkFieldHeaderValue(value string, defaultValue string) string {
|
||||
if value == AccessLogKeep || value == AccessLogDrop || value == AccessLogRedact {
|
||||
return value
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package whitelist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -50,64 +50,78 @@ func NewIP(whiteList []string, insecure bool, useXForwardedFor bool) (*IP, error
|
|||
}
|
||||
|
||||
// IsAuthorized checks if provided request is authorized by the white list
|
||||
func (ip *IP) IsAuthorized(req *http.Request) (bool, net.IP, error) {
|
||||
func (ip *IP) IsAuthorized(req *http.Request) error {
|
||||
if ip.insecure {
|
||||
return true, nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
var invalidMatches []string
|
||||
|
||||
if ip.useXForwardedFor {
|
||||
xFFs := req.Header[XForwardedFor]
|
||||
if len(xFFs) > 1 {
|
||||
if len(xFFs) > 0 {
|
||||
for _, xFF := range xFFs {
|
||||
ok, i, err := ip.contains(parseHost(xFF))
|
||||
ok, err := ip.contains(parseHost(xFF))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
if ok {
|
||||
return ok, i, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
invalidMatches = append(invalidMatches, xFF)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return err
|
||||
}
|
||||
return ip.contains(host)
|
||||
|
||||
ok, err := ip.contains(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
invalidMatches = append(invalidMatches, req.RemoteAddr)
|
||||
return fmt.Errorf("%q matched none of the white list", strings.Join(invalidMatches, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// contains checks if provided address is in the white list
|
||||
func (ip *IP) contains(addr string) (bool, net.IP, error) {
|
||||
func (ip *IP) contains(addr string) (bool, error) {
|
||||
ipAddr, err := parseIP(addr)
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("unable to parse address: %s: %s", addr, err)
|
||||
return false, fmt.Errorf("unable to parse address: %s: %s", addr, err)
|
||||
}
|
||||
|
||||
contains, err := ip.ContainsIP(ipAddr)
|
||||
return contains, ipAddr, err
|
||||
return ip.ContainsIP(ipAddr), nil
|
||||
}
|
||||
|
||||
// ContainsIP checks if provided address is in the white list
|
||||
func (ip *IP) ContainsIP(addr net.IP) (bool, error) {
|
||||
func (ip *IP) ContainsIP(addr net.IP) bool {
|
||||
if ip.insecure {
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
|
||||
for _, whiteListIP := range ip.whiteListsIPs {
|
||||
if whiteListIP.Equal(addr) {
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, whiteListNet := range ip.whiteListsNet {
|
||||
if whiteListNet.Contains(addr) {
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
func parseIP(addr string) (net.IP, error) {
|
||||
|
|
|
@ -17,7 +17,7 @@ func TestIsAuthorized(t *testing.T) {
|
|||
allowXForwardedFor bool
|
||||
remoteAddr string
|
||||
xForwardedForValues []string
|
||||
expected bool
|
||||
authorized bool
|
||||
}{
|
||||
{
|
||||
desc: "allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor in range",
|
||||
|
@ -25,7 +25,7 @@ func TestIsAuthorized(t *testing.T) {
|
|||
allowXForwardedFor: true,
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
|
||||
expected: true,
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor in range",
|
||||
|
@ -33,7 +33,7 @@ func TestIsAuthorized(t *testing.T) {
|
|||
allowXForwardedFor: true,
|
||||
remoteAddr: "1.2.3.1:123",
|
||||
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
|
||||
expected: true,
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor not in range",
|
||||
|
@ -41,7 +41,7 @@ func TestIsAuthorized(t *testing.T) {
|
|||
allowXForwardedFor: true,
|
||||
remoteAddr: "1.2.3.1:123",
|
||||
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
|
||||
expected: true,
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor not in range",
|
||||
|
@ -49,7 +49,7 @@ func TestIsAuthorized(t *testing.T) {
|
|||
allowXForwardedFor: true,
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
|
||||
expected: false,
|
||||
authorized: false,
|
||||
},
|
||||
{
|
||||
desc: "don't allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor in range",
|
||||
|
@ -57,7 +57,7 @@ func TestIsAuthorized(t *testing.T) {
|
|||
allowXForwardedFor: false,
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
|
||||
expected: false,
|
||||
authorized: false,
|
||||
},
|
||||
{
|
||||
desc: "don't allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor in range",
|
||||
|
@ -65,7 +65,7 @@ func TestIsAuthorized(t *testing.T) {
|
|||
allowXForwardedFor: false,
|
||||
remoteAddr: "1.2.3.1:123",
|
||||
xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"},
|
||||
expected: true,
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "don't allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor not in range",
|
||||
|
@ -73,7 +73,7 @@ func TestIsAuthorized(t *testing.T) {
|
|||
allowXForwardedFor: false,
|
||||
remoteAddr: "1.2.3.1:123",
|
||||
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
|
||||
expected: true,
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "don't allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor not in range",
|
||||
|
@ -81,7 +81,7 @@ func TestIsAuthorized(t *testing.T) {
|
|||
allowXForwardedFor: false,
|
||||
remoteAddr: "10.2.3.1:123",
|
||||
xForwardedForValues: []string{"10.2.3.1", "10.2.3.1"},
|
||||
expected: false,
|
||||
authorized: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -95,11 +95,12 @@ func TestIsAuthorized(t *testing.T) {
|
|||
whiteLister, err := NewIP(test.whiteList, false, test.allowXForwardedFor)
|
||||
require.NoError(t, err)
|
||||
|
||||
authorized, ips, err := whiteLister.IsAuthorized(req)
|
||||
err = whiteLister.IsAuthorized(req)
|
||||
if test.authorized {
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, ips)
|
||||
|
||||
assert.Equal(t, test.expected, authorized)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -349,16 +350,14 @@ func TestContainsIsAllowed(t *testing.T) {
|
|||
require.NotNil(t, whiteLister)
|
||||
|
||||
for _, testIP := range test.passIPs {
|
||||
allowed, ip, err := whiteLister.contains(testIP)
|
||||
allowed, err := whiteLister.contains(testIP)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ip, err)
|
||||
assert.Truef(t, allowed, "%s should have passed.", testIP)
|
||||
}
|
||||
|
||||
for _, testIP := range test.rejectIPs {
|
||||
allowed, ip, err := whiteLister.contains(testIP)
|
||||
allowed, err := whiteLister.contains(testIP)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ip, err)
|
||||
assert.Falsef(t, allowed, "%s should not have passed.", testIP)
|
||||
}
|
||||
})
|
||||
|
@ -405,7 +404,7 @@ func TestContainsInsecure(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ok, _, err := test.whiteLister.contains(test.ip)
|
||||
ok, err := test.whiteLister.contains(test.ip)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, ok)
|
||||
|
@ -426,9 +425,8 @@ func TestContainsBrokenIPs(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
for _, testIP := range brokenIPs {
|
||||
_, ip, err := whiteLister.contains(testIP)
|
||||
_, err := whiteLister.contains(testIP)
|
||||
assert.Error(t, err)
|
||||
require.Nil(t, ip, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue