Add rate limiter, rename maxConn into inFlightReq
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com> Co-authored-by: Jean-Baptiste Doumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
parent
a8c73f7baf
commit
4ec90c5c0d
30 changed files with 1419 additions and 651 deletions
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
@ -34,6 +34,10 @@ h3 {
|
||||||
font-weight: bold !important;
|
font-weight: bold !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-typeset h5 {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
figcaption {
|
figcaption {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
|
|
247
docs/content/middlewares/inflightreq.md
Normal file
247
docs/content/middlewares/inflightreq.md
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
# InFlightReq
|
||||||
|
|
||||||
|
Limiting the Number of Simultaneous In-Flight Requests
|
||||||
|
{: .subtitle }
|
||||||
|
|
||||||
|
![InFlightReq](../assets/img/middleware/inflightreq.png)
|
||||||
|
|
||||||
|
To proactively prevent services from being overwhelmed with high load, a limit on the number of simultaneous in-flight requests can be applied.
|
||||||
|
|
||||||
|
## Configuration Examples
|
||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-inflightreq
|
||||||
|
spec:
|
||||||
|
inFlightReq:
|
||||||
|
amount: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.test-inflightreq.inflightreq.amount": "10"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
# Limiting to 10 simultaneous connections
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10"
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
# Limiting to 10 simultaneous connections
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-inflightreq.inFlightReq]
|
||||||
|
amount = 10
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
# Limiting to 10 simultaneous connections
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-inflightreq:
|
||||||
|
inFlightReq:
|
||||||
|
amount: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
### `amount`
|
||||||
|
|
||||||
|
The `amount` option defines the maximum amount of allowed simultaneous in-flight request.
|
||||||
|
The middleware will return an `HTTP 429 Too Many Requests` if there are already `amount` requests in progress (based on the same `sourceCriterion` strategy).
|
||||||
|
|
||||||
|
### `sourceCriterion`
|
||||||
|
|
||||||
|
SourceCriterion defines what criterion is used to group requests as originating from a common source.
|
||||||
|
The precedence order is `ipStrategy`, then `requestHeaderName`, then `requestHost`.
|
||||||
|
If none are set, the default is to use the `requestHost`.
|
||||||
|
|
||||||
|
#### `sourceCriterion.ipStrategy`
|
||||||
|
|
||||||
|
The `ipStrategy` option defines two parameters that sets how Traefik will determine the client IP: `depth`, and `excludedIPs`.
|
||||||
|
|
||||||
|
##### `ipStrategy.depth`
|
||||||
|
|
||||||
|
The `depth` option tells Traefik to use the `X-Forwarded-For` header and take the IP located at the `depth` position (starting from the right).
|
||||||
|
|
||||||
|
- If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP will be empty.
|
||||||
|
- `depth` is ignored if its value is is lesser than or equal to 0.
|
||||||
|
|
||||||
|
!!! note "Example of Depth & X-Forwarded-For"
|
||||||
|
|
||||||
|
If `depth` was equal to 2, and the request `X-Forwarded-For` header was `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP would be `"10.0.0.1"` (at depth 4) but the IP used as the criterion would be `"12.0.0.1"` (`depth=2`).
|
||||||
|
|
||||||
|
| `X-Forwarded-For` | `depth` | clientIP |
|
||||||
|
|-----------------------------------------|---------|--------------|
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` |
|
||||||
|
|
||||||
|
##### `ipStrategy.excludedIPs`
|
||||||
|
|
||||||
|
`excludedIPs` tells Traefik to scan the `X-Forwarded-For` header and pick the first IP not in the list.
|
||||||
|
|
||||||
|
!!! important
|
||||||
|
If `depth` is specified, `excludedIPs` is ignored.
|
||||||
|
|
||||||
|
!!! note "Example of ExcludedIPs & X-Forwarded-For"
|
||||||
|
|
||||||
|
| `X-Forwarded-For` | `excludedIPs` | clientIP |
|
||||||
|
|-----------------------------------------|-----------------------|--------------|
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
|
||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-inflightreq
|
||||||
|
spec:
|
||||||
|
inFlightReq:
|
||||||
|
sourceCriterion:
|
||||||
|
ipStrategy:
|
||||||
|
excludedIPs:
|
||||||
|
- 127.0.0.1/32
|
||||||
|
- 192.168.1.7
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.excludedips": "127.0.0.1/32, 192.168.1.7"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-inflightreq.inflightreq]
|
||||||
|
[http.middlewares.test-inflightreq.inFlightReq.sourceCriterion.ipStrategy]
|
||||||
|
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-inflightreq:
|
||||||
|
inFlightReq:
|
||||||
|
sourceCriterion:
|
||||||
|
ipStrategy:
|
||||||
|
excludedIPs:
|
||||||
|
- "127.0.0.1/32"
|
||||||
|
- "192.168.1.7"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `sourceCriterion.requestHeaderName`
|
||||||
|
|
||||||
|
Requests having the same value for the given header are grouped as coming from the same source.
|
||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername=username"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-inflightreq
|
||||||
|
spec:
|
||||||
|
inFlightReq:
|
||||||
|
sourceCriterion:
|
||||||
|
requestHeaderName: username
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername=username"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername": "username"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-inflightreq.inflightreq]
|
||||||
|
[http.middlewares.test-inflightreq.inFlightReq.sourceCriterion]
|
||||||
|
requestHeaderName = "username"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-inflightreq:
|
||||||
|
inFlightReq:
|
||||||
|
sourceCriterion:
|
||||||
|
requestHeaderName: username
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `sourceCriterion.requestHost`
|
||||||
|
|
||||||
|
Whether to consider the request host as the source.
|
||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-inflightreq
|
||||||
|
spec:
|
||||||
|
inFlightReq:
|
||||||
|
sourceCriterion:
|
||||||
|
requestHost: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost": "true"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-inflightreq.inflightreq]
|
||||||
|
[http.middlewares.test-inflightreq.inFlightReq.sourceCriterion]
|
||||||
|
requestHost = true
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-inflightreq:
|
||||||
|
inFlightReq:
|
||||||
|
sourceCriterion:
|
||||||
|
requestHost: true
|
||||||
|
```
|
|
@ -1,71 +0,0 @@
|
||||||
# MaxConnection
|
|
||||||
|
|
||||||
Limiting the Number of Simultaneous Clients
|
|
||||||
{: .subtitle }
|
|
||||||
|
|
||||||
![MaxConnection](../assets/img/middleware/maxconnection.png)
|
|
||||||
|
|
||||||
To proactively prevent services from being overwhelmed with high load, a maximum connection limit can be applied.
|
|
||||||
|
|
||||||
## Configuration Examples
|
|
||||||
|
|
||||||
```yaml tab="Docker"
|
|
||||||
# Limiting to 10 simultaneous connections
|
|
||||||
labels:
|
|
||||||
- "traefik.http.middlewares.test-maxconn.maxconn.amount=10"
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Kubernetes"
|
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
|
||||||
kind: Middleware
|
|
||||||
metadata:
|
|
||||||
name: test-maxconn
|
|
||||||
spec:
|
|
||||||
maxConn:
|
|
||||||
amount: 10
|
|
||||||
```
|
|
||||||
|
|
||||||
```json tab="Marathon"
|
|
||||||
"labels": {
|
|
||||||
"traefik.http.middlewares.test-maxconn.maxconn.amount": "10"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Rancher"
|
|
||||||
# Limiting to 10 simultaneous connections
|
|
||||||
labels:
|
|
||||||
- "traefik.http.middlewares.test-maxconn.maxconn.amount=10"
|
|
||||||
```
|
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
|
||||||
# Limiting to 10 simultaneous connections
|
|
||||||
[http.middlewares]
|
|
||||||
[http.middlewares.test-maxconn.maxConn]
|
|
||||||
amount = 10
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
|
||||||
# Limiting to 10 simultaneous connections
|
|
||||||
http:
|
|
||||||
middlewares:
|
|
||||||
test-maxconn:
|
|
||||||
maxConn:
|
|
||||||
amount: 10
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration Options
|
|
||||||
|
|
||||||
### `amount`
|
|
||||||
|
|
||||||
The `amount` option defines the maximum amount of allowed simultaneous connections.
|
|
||||||
The middleware will return an `HTTP 429 Too Many Requests` if there are already `amount` requests in progress (based on the same `extractorFunc` strategy).
|
|
||||||
|
|
||||||
### `extractorFunc`
|
|
||||||
|
|
||||||
The `extractorFunc` defines the strategy used to categorize requests.
|
|
||||||
|
|
||||||
The possible values are:
|
|
||||||
|
|
||||||
- `request.host` categorizes requests based on the request host.
|
|
||||||
- `client.ip` categorizes requests based on the client ip.
|
|
||||||
- `request.header.ANY_HEADER` categorizes requests based on the provided `ANY_HEADER` value.
|
|
|
@ -208,7 +208,7 @@ and therefore this specification would be ignored even if present.
|
||||||
| [ForwardAuth](forwardauth.md) | Authentication delegation | Security, Authentication |
|
| [ForwardAuth](forwardauth.md) | Authentication delegation | Security, Authentication |
|
||||||
| [Headers](headers.md) | Add / Update headers | Security |
|
| [Headers](headers.md) | Add / Update headers | Security |
|
||||||
| [IPWhiteList](ipwhitelist.md) | Limit the allowed client IPs | Security, Request lifecycle |
|
| [IPWhiteList](ipwhitelist.md) | Limit the allowed client IPs | Security, Request lifecycle |
|
||||||
| [MaxConnection](maxconnection.md) | Limit the number of simultaneous connections | Security, Request lifecycle |
|
| [InFlightReq](inflightreq.md) | Limit the number of simultaneous connections | Security, Request lifecycle |
|
||||||
| [PassTLSClientCert](passtlsclientcert.md) | Adding Client Certificates in a Header | Security |
|
| [PassTLSClientCert](passtlsclientcert.md) | Adding Client Certificates in a Header | Security |
|
||||||
| [RateLimit](ratelimit.md) | Limit the call frequency | Security, Request lifecycle |
|
| [RateLimit](ratelimit.md) | Limit the call frequency | Security, Request lifecycle |
|
||||||
| [RedirectScheme](redirectscheme.md) | Redirect easily the client elsewhere | Request lifecycle |
|
| [RedirectScheme](redirectscheme.md) | Redirect easily the client elsewhere | Request lifecycle |
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
# RateLimit
|
# RateLimit
|
||||||
|
|
||||||
!!! warning
|
To Control the Number of Requests Going to a Service
|
||||||
This middleware is disable for now.
|
|
||||||
|
|
||||||
Protection from Too Many Calls
|
|
||||||
{: .subtitle }
|
{: .subtitle }
|
||||||
|
|
||||||
![RateLimit](../assets/img/middleware/ratelimit.png)
|
![RateLimit](../assets/img/middleware/ratelimit.png)
|
||||||
|
@ -13,124 +10,337 @@ The RateLimit middleware ensures that services will receive a _fair_ number of r
|
||||||
## Configuration Example
|
## Configuration Example
|
||||||
|
|
||||||
```yaml tab="Docker"
|
```yaml tab="Docker"
|
||||||
# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
|
# Here, an average of 100 requests per second is allowed.
|
||||||
# These can "burst" up to 10 and 200 in each period, respectively.
|
# In addition, a burst of 50 requests is allowed.
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.extractorfunc=client.ip"
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.period=10s"
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=50"
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.average=100"
|
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.burst=200"
|
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.period=3s"
|
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.average=5"
|
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.burst=10"
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes"
|
```yaml tab="Kubernetes"
|
||||||
# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
|
# Here, an average of 100 requests per second is allowed.
|
||||||
# These can "burst" up to 10 and 200 in each period, respectively.
|
# In addition, a burst of 50 requests is allowed.
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: Middleware
|
kind: Middleware
|
||||||
metadata:
|
metadata:
|
||||||
name: test-ratelimit
|
name: test-ratelimit
|
||||||
spec:
|
spec:
|
||||||
rateLimit:
|
rateLimit:
|
||||||
extractorFunc: client.ip
|
average: 100
|
||||||
rateSet:
|
burst: 50
|
||||||
rate0:
|
|
||||||
period: 10s
|
|
||||||
average: 100
|
|
||||||
burst: 200
|
|
||||||
rate1:
|
|
||||||
period: 3s
|
|
||||||
average: 5
|
|
||||||
burst: 10
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```json tab="Marathon"
|
```json tab="Marathon"
|
||||||
"labels": {
|
"labels": {
|
||||||
"traefik.http.middlewares.test-ratelimit.ratelimit.extractorfunc": "client.ip",
|
"traefik.http.middlewares.test-ratelimit.ratelimit.average": "100",
|
||||||
"traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.period": "10s",
|
"traefik.http.middlewares.test-ratelimit.ratelimit.burst": "50"
|
||||||
"traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.average": "100",
|
|
||||||
"traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.burst": "200",
|
|
||||||
"traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.period": "3s",
|
|
||||||
"traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.average": "5",
|
|
||||||
"traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.burst": "10"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Rancher"
|
```yaml tab="Rancher"
|
||||||
# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
|
# Here, an average of 100 requests per second is allowed.
|
||||||
# These can "burst" up to 10 and 200 in each period, respectively.
|
# In addition, a burst of 50 requests is allowed.
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.extractorfunc=client.ip"
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.period=10s"
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=50"
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.average=100"
|
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.burst=200"
|
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.period=3s"
|
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.average=5"
|
|
||||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.burst=10"
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
|
# Here, an average of 100 requests per second is allowed.
|
||||||
# These can "burst" up to 10 and 200 in each period, respectively.
|
# In addition, a burst of 50 requests is allowed.
|
||||||
[http.middlewares]
|
[http.middlewares]
|
||||||
[http.middlewares.test-ratelimit.rateLimit]
|
[http.middlewares.test-ratelimit.rateLimit]
|
||||||
extractorFunc = "client.ip"
|
average = 100
|
||||||
|
burst = 50
|
||||||
[http.middlewares.test-ratelimit.rateLimit.rateSet.rate0]
|
|
||||||
period = "10s"
|
|
||||||
average = 100
|
|
||||||
burst = 200
|
|
||||||
|
|
||||||
[http.middlewares.test-ratelimit.rateLimit.rateSet.rate1]
|
|
||||||
period = "3s"
|
|
||||||
average = 5
|
|
||||||
burst = 10
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
|
# Here, an average of 100 requests per second is allowed.
|
||||||
# These can "burst" up to 10 and 200 in each period, respectively.
|
# In addition, a burst of 50 requests is allowed.
|
||||||
http:
|
http:
|
||||||
middlewares:
|
middlewares:
|
||||||
test-ratelimit:
|
test-ratelimit:
|
||||||
rateLimit:
|
rateLimit:
|
||||||
extractorFunc: "client.ip"
|
average: 100
|
||||||
rateSet:
|
burst: 50
|
||||||
rate0:
|
|
||||||
period: "10s"
|
|
||||||
average: 100
|
|
||||||
burst: 200
|
|
||||||
rate1:
|
|
||||||
period: "3s"
|
|
||||||
average: 5
|
|
||||||
burst: 10
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration Options
|
## Configuration Options
|
||||||
|
|
||||||
### `extractorFunc`
|
### `average`
|
||||||
|
|
||||||
The `extractorFunc` option defines the strategy used to categorize requests.
|
Average is the maximum rate, in requests/s, allowed for the given source.
|
||||||
|
It defaults to 0, which means no rate limiting.
|
||||||
|
|
||||||
The possible values are:
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
|
||||||
|
```
|
||||||
|
|
||||||
- `request.host` categorizes requests based on the request host.
|
```yaml tab="Kubernetes"
|
||||||
- `client.ip` categorizes requests based on the client ip.
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
- `request.header.ANY_HEADER` categorizes requests based on the provided `ANY_HEADER` value.
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-ratelimit
|
||||||
|
spec:
|
||||||
|
rateLimit:
|
||||||
|
average: 100
|
||||||
|
```
|
||||||
|
|
||||||
### `rateSet`
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.test-ratelimit.ratelimit.average": "100",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
You can combine multiple rate limits.
|
```yaml tab="Rancher"
|
||||||
The rate limit will trigger with the first reached limit.
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
|
||||||
|
```
|
||||||
|
|
||||||
Each rate limit has 3 options, `period`, `average`, and `burst`.
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-ratelimit.rateLimit]
|
||||||
|
average = 100
|
||||||
|
```
|
||||||
|
|
||||||
The rate limit will allow an average of `average` requests every `period`, with a maximum of `burst` request on that period.
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-ratelimit:
|
||||||
|
rateLimit:
|
||||||
|
average: 100
|
||||||
|
```
|
||||||
|
|
||||||
!!! note "Period Format"
|
### `burst`
|
||||||
|
|
||||||
Period is to be given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
|
Burst is the maximum number of requests allowed to go through in the same arbitrarily small period of time.
|
||||||
|
It defaults to 1.
|
||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=100"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-ratelimit
|
||||||
|
spec:
|
||||||
|
rateLimit:
|
||||||
|
burst: 100
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.test-ratelimit.ratelimit.burst": "100",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=100"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-ratelimit.rateLimit]
|
||||||
|
burst = 100
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-ratelimit:
|
||||||
|
rateLimit:
|
||||||
|
burst: 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### `sourceCriterion`
|
||||||
|
|
||||||
|
SourceCriterion defines what criterion is used to group requests as originating from a common source.
|
||||||
|
The precedence order is `ipStrategy`, then `requestHeaderName`, then `requestHost`.
|
||||||
|
If none are set, the default is to use the request's remote address field (as an `ipStrategy`).
|
||||||
|
|
||||||
|
#### `sourceCriterion.ipStrategy`
|
||||||
|
|
||||||
|
The `ipStrategy` option defines two parameters that sets how Traefik will determine the client IP: `depth`, and `excludedIPs`.
|
||||||
|
|
||||||
|
##### `ipStrategy.depth`
|
||||||
|
|
||||||
|
The `depth` option tells Traefik to use the `X-Forwarded-For` header and take the IP located at the `depth` position (starting from the right).
|
||||||
|
|
||||||
|
- If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP will be empty.
|
||||||
|
- `depth` is ignored if its value is is lesser than or equal to 0.
|
||||||
|
|
||||||
|
!!! note "Example of Depth & X-Forwarded-For"
|
||||||
|
|
||||||
|
If `depth` was equal to 2, and the request `X-Forwarded-For` header was `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP would be `"10.0.0.1"` (at depth 4) but the IP used as the criterion would be `"12.0.0.1"` (`depth=2`).
|
||||||
|
|
||||||
|
| `X-Forwarded-For` | `depth` | clientIP |
|
||||||
|
|-----------------------------------------|---------|--------------|
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` |
|
||||||
|
|
||||||
|
##### `ipStrategy.excludedIPs`
|
||||||
|
|
||||||
|
`excludedIPs` tells Traefik to scan the `X-Forwarded-For` header and pick the first IP not in the list.
|
||||||
|
|
||||||
|
!!! important
|
||||||
|
If `depth` is specified, `excludedIPs` is ignored.
|
||||||
|
|
||||||
|
!!! note "Example of ExcludedIPs & X-Forwarded-For"
|
||||||
|
|
||||||
|
| `X-Forwarded-For` | `excludedIPs` | clientIP |
|
||||||
|
|-----------------------------------------|-----------------------|--------------|
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
|
||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-ratelimit
|
||||||
|
spec:
|
||||||
|
rateLimit:
|
||||||
|
sourceCriterion:
|
||||||
|
ipStrategy:
|
||||||
|
excludedIPs:
|
||||||
|
- 127.0.0.1/32
|
||||||
|
- 192.168.1.7
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.excludedips": "127.0.0.1/32, 192.168.1.7"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-ratelimit.rateLimit]
|
||||||
|
[http.middlewares.test-ratelimit.rateLimit.sourceCriterion.ipStrategy]
|
||||||
|
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-ratelimit:
|
||||||
|
rateLimit:
|
||||||
|
sourceCriterion:
|
||||||
|
ipStrategy:
|
||||||
|
excludedIPs:
|
||||||
|
- "127.0.0.1/32"
|
||||||
|
- "192.168.1.7"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `sourceCriterion.requestHeaderName`
|
||||||
|
|
||||||
|
Requests having the same value for the given header are grouped as coming from the same source.
|
||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername=username"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-ratelimit
|
||||||
|
spec:
|
||||||
|
rateLimit:
|
||||||
|
sourceCriterion:
|
||||||
|
requestHeaderName: username
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername=username"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername": "username"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-ratelimit.rateLimit]
|
||||||
|
[http.middlewares.test-ratelimit.rateLimit.sourceCriterion]
|
||||||
|
requestHeaderName = "username"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-ratelimit:
|
||||||
|
rateLimit:
|
||||||
|
sourceCriterion:
|
||||||
|
requestHeaderName: username
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `sourceCriterion.requestHost`
|
||||||
|
|
||||||
|
Whether to consider the request host as the source.
|
||||||
|
|
||||||
|
```yaml tab="Docker"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-ratelimit
|
||||||
|
spec:
|
||||||
|
rateLimit:
|
||||||
|
sourceCriterion:
|
||||||
|
requestHost: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Rancher"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json tab="Marathon"
|
||||||
|
"labels": {
|
||||||
|
"traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost": "true"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-ratelimit.rateLimit]
|
||||||
|
[http.middlewares.test-ratelimit.rateLimit.sourceCriterion]
|
||||||
|
requestHost = true
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-ratelimit:
|
||||||
|
rateLimit:
|
||||||
|
sourceCriterion:
|
||||||
|
requestHost: true
|
||||||
|
```
|
|
@ -64,8 +64,12 @@
|
||||||
- "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.depth=42"
|
- "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.depth=42"
|
||||||
- "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.excludedips=foobar, foobar"
|
- "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.excludedips=foobar, foobar"
|
||||||
- "traefik.http.middlewares.middleware10.ipwhitelist.sourcerange=foobar, foobar"
|
- "traefik.http.middlewares.middleware10.ipwhitelist.sourcerange=foobar, foobar"
|
||||||
- "traefik.http.middlewares.middleware11.maxconn.amount=42"
|
- "traefik.http.middlewares.middleware11.inflightreq.amount=42"
|
||||||
- "traefik.http.middlewares.middleware11.maxconn.extractorfunc=foobar"
|
- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requestheadername=foobar"
|
||||||
|
- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requesthost=true"
|
||||||
|
- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.ipstrategy.depth=42"
|
||||||
|
- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar"
|
||||||
|
- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requesthost=true"
|
||||||
- "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.commonname=true"
|
- "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.commonname=true"
|
||||||
- "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.country=true"
|
- "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.country=true"
|
||||||
- "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.domaincomponent=true"
|
- "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.domaincomponent=true"
|
||||||
|
|
|
@ -170,9 +170,14 @@
|
||||||
depth = 42
|
depth = 42
|
||||||
excludedIPs = ["foobar", "foobar"]
|
excludedIPs = ["foobar", "foobar"]
|
||||||
[http.middlewares.Middleware11]
|
[http.middlewares.Middleware11]
|
||||||
[http.middlewares.Middleware11.maxConn]
|
[http.middlewares.Middleware11.inFlightReq]
|
||||||
amount = 42
|
amount = 42
|
||||||
extractorFunc = "foobar"
|
[http.middlewares.Middleware11.inFlightReq.sourceCriterion]
|
||||||
|
requestHeaderName = "foobar"
|
||||||
|
requestHost = true
|
||||||
|
[http.middlewares.Middleware11.inFlightReq.sourceCriterion.ipStrategy]
|
||||||
|
depth = 42
|
||||||
|
excludedIPs = ["foobar", "foobar"]
|
||||||
[http.middlewares.Middleware12]
|
[http.middlewares.Middleware12]
|
||||||
[http.middlewares.Middleware12.passTLSClientCert]
|
[http.middlewares.Middleware12.passTLSClientCert]
|
||||||
pem = true
|
pem = true
|
||||||
|
|
|
@ -198,9 +198,14 @@ http:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
Middleware11:
|
Middleware11:
|
||||||
maxConn:
|
inFlightReq:
|
||||||
amount: 42
|
amount: 42
|
||||||
extractorFunc: foobar
|
sourceCriterion:
|
||||||
|
ipStrategy:
|
||||||
|
depth: 42
|
||||||
|
excludedIPs: [ foobar, foobar ]
|
||||||
|
requestHeaderName: foobar
|
||||||
|
requestHost: true
|
||||||
Middleware12:
|
Middleware12:
|
||||||
passTLSClientCert:
|
passTLSClientCert:
|
||||||
pem: true
|
pem: true
|
||||||
|
@ -254,6 +259,16 @@ http:
|
||||||
regex:
|
regex:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
|
Middleware20:
|
||||||
|
rateLimit:
|
||||||
|
average: 42
|
||||||
|
burst: 42
|
||||||
|
sourceCriterion:
|
||||||
|
ipStrategy:
|
||||||
|
depth: 42
|
||||||
|
excludedIPs: [ foobar, foobar ]
|
||||||
|
requestHeaderName: foobar
|
||||||
|
requestHost: true
|
||||||
tcp:
|
tcp:
|
||||||
routers:
|
routers:
|
||||||
TCPRouter0:
|
TCPRouter0:
|
||||||
|
|
|
@ -64,8 +64,11 @@
|
||||||
"traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.depth": "42",
|
"traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.depth": "42",
|
||||||
"traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.excludedips": "foobar, foobar",
|
"traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.excludedips": "foobar, foobar",
|
||||||
"traefik.http.middlewares.middleware10.ipwhitelist.sourcerange": "foobar, foobar",
|
"traefik.http.middlewares.middleware10.ipwhitelist.sourcerange": "foobar, foobar",
|
||||||
"traefik.http.middlewares.middleware11.maxconn.amount": "42",
|
"traefik.http.middlewares.Middleware11.inflightreq.amount": "42",
|
||||||
"traefik.http.middlewares.middleware11.maxconn.extractorfunc": "foobar",
|
"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.ipstrategy.depth": "42",
|
||||||
|
"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.ipstrategy.excludedips": "foobar, fiibar",
|
||||||
|
"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.requestheadername": "foobar",
|
||||||
|
"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.requesthost": "true",
|
||||||
"traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.commonname": "true",
|
"traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.commonname": "true",
|
||||||
"traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.country": "true",
|
"traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.country": "true",
|
||||||
"traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.domaincomponent": "true",
|
"traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.domaincomponent": "true",
|
||||||
|
@ -96,6 +99,12 @@
|
||||||
"traefik.http.middlewares.middleware17.retry.attempts": "42",
|
"traefik.http.middlewares.middleware17.retry.attempts": "42",
|
||||||
"traefik.http.middlewares.middleware18.stripprefix.prefixes": "foobar, foobar",
|
"traefik.http.middlewares.middleware18.stripprefix.prefixes": "foobar, foobar",
|
||||||
"traefik.http.middlewares.middleware19.stripprefixregex.regex": "foobar, foobar",
|
"traefik.http.middlewares.middleware19.stripprefixregex.regex": "foobar, foobar",
|
||||||
|
"traefik.http.middlewares.Middleware20.ratelimit.average": "42",
|
||||||
|
"traefik.http.middlewares.Middleware20.ratelimit.burst": "42",
|
||||||
|
"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.requestheadername": "foobar",
|
||||||
|
"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.requesthost": "true",
|
||||||
|
"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.ipstrategy.depth": "42",
|
||||||
|
"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.ipstrategy.excludedips": "foobar, foobar",
|
||||||
"traefik.http.routers.router0.entrypoints": "foobar, foobar",
|
"traefik.http.routers.router0.entrypoints": "foobar, foobar",
|
||||||
"traefik.http.routers.router0.middlewares": "foobar, foobar",
|
"traefik.http.routers.router0.middlewares": "foobar, foobar",
|
||||||
"traefik.http.routers.router0.priority": "42",
|
"traefik.http.routers.router0.priority": "42",
|
||||||
|
|
|
@ -102,7 +102,7 @@ nav:
|
||||||
- 'ForwardAuth': 'middlewares/forwardauth.md'
|
- 'ForwardAuth': 'middlewares/forwardauth.md'
|
||||||
- 'Headers': 'middlewares/headers.md'
|
- 'Headers': 'middlewares/headers.md'
|
||||||
- 'IpWhitelist': 'middlewares/ipwhitelist.md'
|
- 'IpWhitelist': 'middlewares/ipwhitelist.md'
|
||||||
- 'Maxconn': 'middlewares/maxconnection.md'
|
- 'InFlightReq': 'middlewares/inflightreq.md'
|
||||||
- 'PassTLSClientCert': 'middlewares/passtlsclientcert.md'
|
- 'PassTLSClientCert': 'middlewares/passtlsclientcert.md'
|
||||||
- 'RateLimit': 'middlewares/ratelimit.md'
|
- 'RateLimit': 'middlewares/ratelimit.md'
|
||||||
- 'RedirectRegex': 'middlewares/redirectregex.md'
|
- 'RedirectRegex': 'middlewares/redirectregex.md'
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -57,6 +57,7 @@ require (
|
||||||
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591
|
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591
|
||||||
github.com/looplab/fsm v0.1.0 // indirect
|
github.com/looplab/fsm v0.1.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.1 // indirect
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
|
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
|
||||||
github.com/miekg/dns v1.1.15
|
github.com/miekg/dns v1.1.15
|
||||||
github.com/mitchellh/copystructure v1.0.0
|
github.com/mitchellh/copystructure v1.0.0
|
||||||
github.com/mitchellh/hashstructure v1.0.0
|
github.com/mitchellh/hashstructure v1.0.0
|
||||||
|
@ -90,6 +91,7 @@ require (
|
||||||
github.com/vulcand/predicate v1.1.0
|
github.com/vulcand/predicate v1.1.0
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect
|
||||||
google.golang.org/grpc v1.22.1
|
google.golang.org/grpc v1.22.1
|
||||||
gopkg.in/DataDog/dd-trace-go.v1 v1.16.1
|
gopkg.in/DataDog/dd-trace-go.v1 v1.16.1
|
||||||
|
|
|
@ -24,15 +24,8 @@
|
||||||
|
|
||||||
[http.middlewares]
|
[http.middlewares]
|
||||||
[http.middlewares.ratelimit.rateLimit]
|
[http.middlewares.ratelimit.rateLimit]
|
||||||
extractorfunc = "client.ip"
|
average = 100
|
||||||
[http.middlewares.ratelimit.rateLimit.rateSet.rateset1]
|
burst = 1
|
||||||
period = "60s"
|
|
||||||
average = 4
|
|
||||||
burst = 5
|
|
||||||
[http.middlewares.ratelimit.rateLimit.rateSet.rateset2]
|
|
||||||
period = "3s"
|
|
||||||
average = 1
|
|
||||||
burst = 2
|
|
||||||
|
|
||||||
[http.services]
|
[http.services]
|
||||||
[http.services.service1]
|
[http.services.service1]
|
||||||
|
|
|
@ -54,8 +54,7 @@ func init() {
|
||||||
check.Suite(&LogRotationSuite{})
|
check.Suite(&LogRotationSuite{})
|
||||||
check.Suite(&MarathonSuite{})
|
check.Suite(&MarathonSuite{})
|
||||||
check.Suite(&MarathonSuite15{})
|
check.Suite(&MarathonSuite15{})
|
||||||
// TODO: disable temporarily
|
check.Suite(&RateLimitSuite{})
|
||||||
// check.Suite(&RateLimitSuite{})
|
|
||||||
check.Suite(&RestSuite{})
|
check.Suite(&RestSuite{})
|
||||||
check.Suite(&RetrySuite{})
|
check.Suite(&RetrySuite{})
|
||||||
check.Suite(&SimpleSuite{})
|
check.Suite(&SimpleSuite{})
|
||||||
|
|
|
@ -37,29 +37,19 @@ func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("ratelimit"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("ratelimit"))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
start := time.Now()
|
||||||
c.Assert(err, checker.IsNil)
|
count := 0
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
for {
|
||||||
c.Assert(err, checker.IsNil)
|
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(err, checker.IsNil)
|
count++
|
||||||
|
if count > 100 {
|
||||||
// sleep for 4 seconds to be certain the configured time period has elapsed
|
break
|
||||||
// then test another request and verify a 200 status code
|
}
|
||||||
time.Sleep(4 * time.Second)
|
}
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
stop := time.Now()
|
||||||
c.Assert(err, checker.IsNil)
|
elapsed := stop.Sub(start)
|
||||||
|
if elapsed < time.Second*99/100 {
|
||||||
// continue requests at 3 second intervals to test the other rate limit time period
|
c.Fatalf("requests throughput was too fast wrt to rate limiting: 100 requests in %v", elapsed)
|
||||||
time.Sleep(3 * time.Second)
|
}
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,9 +295,14 @@
|
||||||
key = "foobar"
|
key = "foobar"
|
||||||
insecureSkipVerify = true
|
insecureSkipVerify = true
|
||||||
[http.middlewares.Middleware16]
|
[http.middlewares.Middleware16]
|
||||||
[http.middlewares.Middleware16.maxConn]
|
[http.middlewares.Middleware16.inFlightReq]
|
||||||
amount = 42
|
amount = 42
|
||||||
extractorFunc = "foobar"
|
[http.middlewares.Middleware16.inFlightReq.sourceCriterion]
|
||||||
|
requestHeaderName = "foobar"
|
||||||
|
requestHost = true
|
||||||
|
[http.middlewares.Middleware16.inFlightReq.sourceCriterion.ipStrategy]
|
||||||
|
depth = 42
|
||||||
|
excludedIPs = ["foobar", "foobar"]
|
||||||
[http.middlewares.Middleware17]
|
[http.middlewares.Middleware17]
|
||||||
[http.middlewares.Middleware17.buffering]
|
[http.middlewares.Middleware17.buffering]
|
||||||
maxRequestBodyBytes = 42
|
maxRequestBodyBytes = 42
|
||||||
|
|
|
@ -8,30 +8,28 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/ip"
|
"github.com/containous/traefik/v2/pkg/ip"
|
||||||
"github.com/containous/traefik/v2/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// Middleware holds the Middleware configuration.
|
// Middleware holds the Middleware configuration.
|
||||||
type Middleware struct {
|
type Middleware struct {
|
||||||
AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty"`
|
AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty"`
|
||||||
StripPrefix *StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty"`
|
StripPrefix *StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty"`
|
||||||
StripPrefixRegex *StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty"`
|
StripPrefixRegex *StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty"`
|
||||||
ReplacePath *ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty"`
|
ReplacePath *ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty"`
|
||||||
ReplacePathRegex *ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty"`
|
ReplacePathRegex *ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty"`
|
||||||
Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty"`
|
Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty"`
|
||||||
IPWhiteList *IPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty"`
|
IPWhiteList *IPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty"`
|
||||||
Headers *Headers `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"`
|
Headers *Headers `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"`
|
||||||
Errors *ErrorPage `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty"`
|
Errors *ErrorPage `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty"`
|
||||||
// TODO: disable temporarily
|
RateLimit *RateLimit `json:"rateLimit,omitempty" toml:"rateLimit,omitempty" yaml:"rateLimit,omitempty"`
|
||||||
// RateLimit *RateLimit `json:"rateLimit,omitempty" toml:"rateLimit,omitempty" yaml:"rateLimit,omitempty"`
|
|
||||||
RedirectRegex *RedirectRegex `json:"redirectRegex,omitempty" toml:"redirectRegex,omitempty" yaml:"redirectRegex,omitempty"`
|
RedirectRegex *RedirectRegex `json:"redirectRegex,omitempty" toml:"redirectRegex,omitempty" yaml:"redirectRegex,omitempty"`
|
||||||
RedirectScheme *RedirectScheme `json:"redirectScheme,omitempty" toml:"redirectScheme,omitempty" yaml:"redirectScheme,omitempty"`
|
RedirectScheme *RedirectScheme `json:"redirectScheme,omitempty" toml:"redirectScheme,omitempty" yaml:"redirectScheme,omitempty"`
|
||||||
BasicAuth *BasicAuth `json:"basicAuth,omitempty" toml:"basicAuth,omitempty" yaml:"basicAuth,omitempty"`
|
BasicAuth *BasicAuth `json:"basicAuth,omitempty" toml:"basicAuth,omitempty" yaml:"basicAuth,omitempty"`
|
||||||
DigestAuth *DigestAuth `json:"digestAuth,omitempty" toml:"digestAuth,omitempty" yaml:"digestAuth,omitempty"`
|
DigestAuth *DigestAuth `json:"digestAuth,omitempty" toml:"digestAuth,omitempty" yaml:"digestAuth,omitempty"`
|
||||||
ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty" toml:"forwardAuth,omitempty" yaml:"forwardAuth,omitempty"`
|
ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty" toml:"forwardAuth,omitempty" yaml:"forwardAuth,omitempty"`
|
||||||
MaxConn *MaxConn `json:"maxConn,omitempty" toml:"maxConn,omitempty" yaml:"maxConn,omitempty"`
|
InFlightReq *InFlightReq `json:"inFlightReq,omitempty" toml:"inFlightReq,omitempty" yaml:"inFlightReq,omitempty"`
|
||||||
Buffering *Buffering `json:"buffering,omitempty" toml:"buffering,omitempty" yaml:"buffering,omitempty"`
|
Buffering *Buffering `json:"buffering,omitempty" toml:"buffering,omitempty" yaml:"buffering,omitempty"`
|
||||||
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty" toml:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"`
|
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty" toml:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"`
|
||||||
Compress *Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty"`
|
Compress *Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty"`
|
||||||
|
@ -219,10 +217,11 @@ func (h *Headers) HasSecureHeadersDefined() bool {
|
||||||
type IPStrategy struct {
|
type IPStrategy struct {
|
||||||
Depth int `json:"depth,omitempty" toml:"depth,omitempty" yaml:"depth,omitempty" export:"true"`
|
Depth int `json:"depth,omitempty" toml:"depth,omitempty" yaml:"depth,omitempty" export:"true"`
|
||||||
ExcludedIPs []string `json:"excludedIPs,omitempty" toml:"excludedIPs,omitempty" yaml:"excludedIPs,omitempty"`
|
ExcludedIPs []string `json:"excludedIPs,omitempty" toml:"excludedIPs,omitempty" yaml:"excludedIPs,omitempty"`
|
||||||
|
// TODO(mpl): I think we should make RemoteAddr an explicit field. For one thing, it would yield better documentation.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an IP selection strategy
|
// Get an IP selection strategy.
|
||||||
// if nil return the RemoteAddr strategy
|
// If nil return the RemoteAddr strategy
|
||||||
// else return a strategy base on the configuration using the X-Forwarded-For Header.
|
// else return a strategy base on the configuration using the X-Forwarded-For Header.
|
||||||
// Depth override the ExcludedIPs
|
// Depth override the ExcludedIPs
|
||||||
func (s *IPStrategy) Get() (ip.Strategy, error) {
|
func (s *IPStrategy) Get() (ip.Strategy, error) {
|
||||||
|
@ -259,15 +258,17 @@ type IPWhiteList struct {
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// MaxConn holds maximum connection configuration.
|
// InFlightReq limits the number of requests being processed and served concurrently.
|
||||||
type MaxConn struct {
|
type InFlightReq struct {
|
||||||
Amount int64 `json:"amount,omitempty" toml:"amount,omitempty" yaml:"amount,omitempty"`
|
Amount int64 `json:"amount,omitempty" toml:"amount,omitempty" yaml:"amount,omitempty"`
|
||||||
ExtractorFunc string `json:"extractorFunc,omitempty" toml:"extractorFunc,omitempty" yaml:"extractorFunc,omitempty"`
|
SourceCriterion *SourceCriterion `json:"sourceCriterion,omitempty" toml:"sourceCriterion,omitempty" yaml:"sourceCriterion,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults Default values for a MaxConn.
|
// SetDefaults Default values for a InFlightReq.
|
||||||
func (m *MaxConn) SetDefaults() {
|
func (i *InFlightReq) SetDefaults() {
|
||||||
m.ExtractorFunc = "request.host"
|
i.SourceCriterion = &SourceCriterion{
|
||||||
|
RequestHost: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
@ -280,25 +281,34 @@ type PassTLSClientCert struct {
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// Rate holds the rate limiting configuration for a specific time period.
|
// SourceCriterion defines what criterion is used to group requests as originating from a common source.
|
||||||
type Rate struct {
|
// The precedence order is IPStrategy, then RequestHeaderName.
|
||||||
Period types.Duration `json:"period,omitempty" toml:"period,omitempty" yaml:"period,omitempty"`
|
// If none are set, the default is to use the request's remote address field.
|
||||||
Average int64 `json:"average,omitempty" toml:"average,omitempty" yaml:"average,omitempty"`
|
type SourceCriterion struct {
|
||||||
Burst int64 `json:"burst,omitempty" toml:"burst,omitempty" yaml:"burst,omitempty"`
|
IPStrategy *IPStrategy `json:"ipStrategy" toml:"ipStrategy, omitempty"`
|
||||||
|
RequestHeaderName string `json:"requestHeaderName,omitempty" toml:"requestHeaderName,omitempty" yaml:"requestHeaderName,omitempty"`
|
||||||
|
RequestHost bool `json:"requestHost,omitempty" toml:"requestHost,omitempty" yaml:"requestHost,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// RateLimit holds the rate limiting configuration for a given frontend.
|
// RateLimit holds the rate limiting configuration for a given router.
|
||||||
type RateLimit struct {
|
type RateLimit struct {
|
||||||
RateSet map[string]*Rate `json:"rateSet,omitempty" toml:"rateSet,omitempty" yaml:"rateSet,omitempty"`
|
// Average is the maximum rate, in requests/s, allowed for the given source.
|
||||||
// FIXME replace by ipStrategy see oxy and replace
|
// It defaults to 0, which means no rate limiting.
|
||||||
ExtractorFunc string `json:"extractorFunc,omitempty" toml:"extractorFunc,omitempty" yaml:"extractorFunc,omitempty"`
|
Average int64 `json:"average,omitempty" toml:"average,omitempty" yaml:"average,omitempty"`
|
||||||
|
// Burst is the maximum number of requests allowed to arrive in the same arbitrarily small period of time.
|
||||||
|
// It defaults to 1.
|
||||||
|
Burst int64 `json:"burst,omitempty" toml:"burst,omitempty" yaml:"burst,omitempty"`
|
||||||
|
SourceCriterion *SourceCriterion `json:"sourceCriterion,omitempty" toml:"sourceCriterion,omitempty" yaml:"sourceCriterion,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults Default values for a MaxConn.
|
// SetDefaults sets the default values on a RateLimit.
|
||||||
func (r *RateLimit) SetDefaults() {
|
func (r *RateLimit) SetDefaults() {
|
||||||
r.ExtractorFunc = "request.host"
|
r.Burst = 1
|
||||||
|
r.SourceCriterion = &SourceCriterion{
|
||||||
|
IPStrategy: &IPStrategy{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
@ -398,30 +408,30 @@ type ClientTLS struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTLSConfig creates a TLS config from ClientTLS structures.
|
// CreateTLSConfig creates a TLS config from ClientTLS structures.
|
||||||
func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
|
func (c *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
|
||||||
if clientTLS == nil {
|
if c == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
caPool := x509.NewCertPool()
|
caPool := x509.NewCertPool()
|
||||||
clientAuth := tls.NoClientCert
|
clientAuth := tls.NoClientCert
|
||||||
if clientTLS.CA != "" {
|
if c.CA != "" {
|
||||||
var ca []byte
|
var ca []byte
|
||||||
if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
|
if _, errCA := os.Stat(c.CA); errCA == nil {
|
||||||
ca, err = ioutil.ReadFile(clientTLS.CA)
|
ca, err = ioutil.ReadFile(c.CA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read CA. %s", err)
|
return nil, fmt.Errorf("failed to read CA. %s", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ca = []byte(clientTLS.CA)
|
ca = []byte(c.CA)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !caPool.AppendCertsFromPEM(ca) {
|
if !caPool.AppendCertsFromPEM(ca) {
|
||||||
return nil, fmt.Errorf("failed to parse CA")
|
return nil, fmt.Errorf("failed to parse CA")
|
||||||
}
|
}
|
||||||
|
|
||||||
if clientTLS.CAOptional {
|
if c.CAOptional {
|
||||||
clientAuth = tls.VerifyClientCertIfGiven
|
clientAuth = tls.VerifyClientCertIfGiven
|
||||||
} else {
|
} else {
|
||||||
clientAuth = tls.RequireAndVerifyClientCert
|
clientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
@ -429,16 +439,16 @@ func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := tls.Certificate{}
|
cert := tls.Certificate{}
|
||||||
_, errKeyIsFile := os.Stat(clientTLS.Key)
|
_, errKeyIsFile := os.Stat(c.Key)
|
||||||
|
|
||||||
if !clientTLS.InsecureSkipVerify && (len(clientTLS.Cert) == 0 || len(clientTLS.Key) == 0) {
|
if !c.InsecureSkipVerify && (len(c.Cert) == 0 || len(c.Key) == 0) {
|
||||||
return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
|
return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(clientTLS.Cert) > 0 && len(clientTLS.Key) > 0 {
|
if len(c.Cert) > 0 && len(c.Key) > 0 {
|
||||||
if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil {
|
if _, errCertIsFile := os.Stat(c.Cert); errCertIsFile == nil {
|
||||||
if errKeyIsFile == nil {
|
if errKeyIsFile == nil {
|
||||||
cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key)
|
cert, err = tls.LoadX509KeyPair(c.Cert, c.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
|
return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -447,7 +457,7 @@ func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if errKeyIsFile != nil {
|
if errKeyIsFile != nil {
|
||||||
cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key))
|
cert, err = tls.X509KeyPair([]byte(c.Cert), []byte(c.Key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
|
return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
|
||||||
|
|
||||||
|
@ -461,7 +471,7 @@ func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
RootCAs: caPool,
|
RootCAs: caPool,
|
||||||
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
|
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||||
ClientAuth: clientAuth,
|
ClientAuth: clientAuth,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -525,17 +525,22 @@ func (in *IPWhiteList) DeepCopy() *IPWhiteList {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *MaxConn) DeepCopyInto(out *MaxConn) {
|
func (in *InFlightReq) DeepCopyInto(out *InFlightReq) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.SourceCriterion != nil {
|
||||||
|
in, out := &in.SourceCriterion, &out.SourceCriterion
|
||||||
|
*out = new(SourceCriterion)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MaxConn.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InFlightReq.
|
||||||
func (in *MaxConn) DeepCopy() *MaxConn {
|
func (in *InFlightReq) DeepCopy() *InFlightReq {
|
||||||
if in == nil {
|
if in == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out := new(MaxConn)
|
out := new(InFlightReq)
|
||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
@ -609,6 +614,11 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
|
||||||
*out = new(ErrorPage)
|
*out = new(ErrorPage)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.RateLimit != nil {
|
||||||
|
in, out := &in.RateLimit, &out.RateLimit
|
||||||
|
*out = new(RateLimit)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
if in.RedirectRegex != nil {
|
if in.RedirectRegex != nil {
|
||||||
in, out := &in.RedirectRegex, &out.RedirectRegex
|
in, out := &in.RedirectRegex, &out.RedirectRegex
|
||||||
*out = new(RedirectRegex)
|
*out = new(RedirectRegex)
|
||||||
|
@ -634,10 +644,10 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
|
||||||
*out = new(ForwardAuth)
|
*out = new(ForwardAuth)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
if in.MaxConn != nil {
|
if in.InFlightReq != nil {
|
||||||
in, out := &in.MaxConn, &out.MaxConn
|
in, out := &in.InFlightReq, &out.InFlightReq
|
||||||
*out = new(MaxConn)
|
*out = new(InFlightReq)
|
||||||
**out = **in
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
if in.Buffering != nil {
|
if in.Buffering != nil {
|
||||||
in, out := &in.Buffering, &out.Buffering
|
in, out := &in.Buffering, &out.Buffering
|
||||||
|
@ -698,39 +708,13 @@ func (in *PassTLSClientCert) DeepCopy() *PassTLSClientCert {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *Rate) DeepCopyInto(out *Rate) {
|
|
||||||
*out = *in
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rate.
|
|
||||||
func (in *Rate) DeepCopy() *Rate {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(Rate)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *RateLimit) DeepCopyInto(out *RateLimit) {
|
func (in *RateLimit) DeepCopyInto(out *RateLimit) {
|
||||||
*out = *in
|
*out = *in
|
||||||
if in.RateSet != nil {
|
if in.SourceCriterion != nil {
|
||||||
in, out := &in.RateSet, &out.RateSet
|
in, out := &in.SourceCriterion, &out.SourceCriterion
|
||||||
*out = make(map[string]*Rate, len(*in))
|
*out = new(SourceCriterion)
|
||||||
for key, val := range *in {
|
(*in).DeepCopyInto(*out)
|
||||||
var outVal *Rate
|
|
||||||
if val == nil {
|
|
||||||
(*out)[key] = nil
|
|
||||||
} else {
|
|
||||||
in, out := &val, &outVal
|
|
||||||
*out = new(Rate)
|
|
||||||
**out = **in
|
|
||||||
}
|
|
||||||
(*out)[key] = outVal
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -996,6 +980,27 @@ func (in *Service) DeepCopy() *Service {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *SourceCriterion) DeepCopyInto(out *SourceCriterion) {
|
||||||
|
*out = *in
|
||||||
|
if in.IPStrategy != nil {
|
||||||
|
in, out := &in.IPStrategy, &out.IPStrategy
|
||||||
|
*out = new(IPStrategy)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceCriterion.
|
||||||
|
func (in *SourceCriterion) DeepCopy() *SourceCriterion {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(SourceCriterion)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Sticky) DeepCopyInto(out *Sticky) {
|
func (in *Sticky) DeepCopyInto(out *Sticky) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@ -1295,6 +1300,11 @@ func (in Users) DeepCopy() Users {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *WRRService) DeepCopyInto(out *WRRService) {
|
func (in *WRRService) DeepCopyInto(out *WRRService) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.Weight != nil {
|
||||||
|
in, out := &in.Weight, &out.Weight
|
||||||
|
*out = new(int)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1314,7 +1324,9 @@ func (in *WeightedRoundRobin) DeepCopyInto(out *WeightedRoundRobin) {
|
||||||
if in.Services != nil {
|
if in.Services != nil {
|
||||||
in, out := &in.Services, &out.Services
|
in, out := &in.Services, &out.Services
|
||||||
*out = make([]WRRService, len(*in))
|
*out = make([]WRRService, len(*in))
|
||||||
copy(*out, *in)
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if in.Sticky != nil {
|
if in.Sticky != nil {
|
||||||
in, out := &in.Sticky, &out.Sticky
|
in, out := &in.Sticky, &out.Sticky
|
||||||
|
|
|
@ -287,9 +287,14 @@
|
||||||
key = "foobar"
|
key = "foobar"
|
||||||
insecureSkipVerify = true
|
insecureSkipVerify = true
|
||||||
[http.middlewares.Middleware16]
|
[http.middlewares.Middleware16]
|
||||||
[http.middlewares.Middleware16.maxConn]
|
[http.middlewares.Middleware16.inFlightReq]
|
||||||
amount = 42
|
amount = 42
|
||||||
extractorFunc = "foobar"
|
[http.middlewares.Middleware16.inFlightReq.sourceCriterion]
|
||||||
|
requestHeaderName = "foobar"
|
||||||
|
requestHost = true
|
||||||
|
[http.middlewares.Middleware16.inFlightReq.sourceCriterion.ipStrategy]
|
||||||
|
depth = 42
|
||||||
|
excludedIPs = ["foobar", "foobar"]
|
||||||
[http.middlewares.Middleware17]
|
[http.middlewares.Middleware17]
|
||||||
[http.middlewares.Middleware17.buffering]
|
[http.middlewares.Middleware17.buffering]
|
||||||
maxRequestBodyBytes = 42
|
maxRequestBodyBytes = 42
|
||||||
|
|
|
@ -11,112 +11,113 @@ import (
|
||||||
|
|
||||||
func TestDecodeConfiguration(t *testing.T) {
|
func TestDecodeConfiguration(t *testing.T) {
|
||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
"traefik.http.middlewares.Middleware0.addprefix.prefix": "foobar",
|
"traefik.http.middlewares.Middleware0.addprefix.prefix": "foobar",
|
||||||
"traefik.http.middlewares.Middleware1.basicauth.headerfield": "foobar",
|
"traefik.http.middlewares.Middleware1.basicauth.headerfield": "foobar",
|
||||||
"traefik.http.middlewares.Middleware1.basicauth.realm": "foobar",
|
"traefik.http.middlewares.Middleware1.basicauth.realm": "foobar",
|
||||||
"traefik.http.middlewares.Middleware1.basicauth.removeheader": "true",
|
"traefik.http.middlewares.Middleware1.basicauth.removeheader": "true",
|
||||||
"traefik.http.middlewares.Middleware1.basicauth.users": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware1.basicauth.users": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware1.basicauth.usersfile": "foobar",
|
"traefik.http.middlewares.Middleware1.basicauth.usersfile": "foobar",
|
||||||
"traefik.http.middlewares.Middleware2.buffering.maxrequestbodybytes": "42",
|
"traefik.http.middlewares.Middleware2.buffering.maxrequestbodybytes": "42",
|
||||||
"traefik.http.middlewares.Middleware2.buffering.maxresponsebodybytes": "42",
|
"traefik.http.middlewares.Middleware2.buffering.maxresponsebodybytes": "42",
|
||||||
"traefik.http.middlewares.Middleware2.buffering.memrequestbodybytes": "42",
|
"traefik.http.middlewares.Middleware2.buffering.memrequestbodybytes": "42",
|
||||||
"traefik.http.middlewares.Middleware2.buffering.memresponsebodybytes": "42",
|
"traefik.http.middlewares.Middleware2.buffering.memresponsebodybytes": "42",
|
||||||
"traefik.http.middlewares.Middleware2.buffering.retryexpression": "foobar",
|
"traefik.http.middlewares.Middleware2.buffering.retryexpression": "foobar",
|
||||||
"traefik.http.middlewares.Middleware3.chain.middlewares": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware3.chain.middlewares": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware4.circuitbreaker.expression": "foobar",
|
"traefik.http.middlewares.Middleware4.circuitbreaker.expression": "foobar",
|
||||||
"traefik.http.middlewares.Middleware5.digestauth.headerfield": "foobar",
|
"traefik.http.middlewares.Middleware5.digestauth.headerfield": "foobar",
|
||||||
"traefik.http.middlewares.Middleware5.digestauth.realm": "foobar",
|
"traefik.http.middlewares.Middleware5.digestauth.realm": "foobar",
|
||||||
"traefik.http.middlewares.Middleware5.digestauth.removeheader": "true",
|
"traefik.http.middlewares.Middleware5.digestauth.removeheader": "true",
|
||||||
"traefik.http.middlewares.Middleware5.digestauth.users": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware5.digestauth.users": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware5.digestauth.usersfile": "foobar",
|
"traefik.http.middlewares.Middleware5.digestauth.usersfile": "foobar",
|
||||||
"traefik.http.middlewares.Middleware6.errors.query": "foobar",
|
"traefik.http.middlewares.Middleware6.errors.query": "foobar",
|
||||||
"traefik.http.middlewares.Middleware6.errors.service": "foobar",
|
"traefik.http.middlewares.Middleware6.errors.service": "foobar",
|
||||||
"traefik.http.middlewares.Middleware6.errors.status": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware6.errors.status": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware7.forwardauth.address": "foobar",
|
"traefik.http.middlewares.Middleware7.forwardauth.address": "foobar",
|
||||||
"traefik.http.middlewares.Middleware7.forwardauth.authresponseheaders": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware7.forwardauth.authresponseheaders": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.ca": "foobar",
|
"traefik.http.middlewares.Middleware7.forwardauth.tls.ca": "foobar",
|
||||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.caoptional": "true",
|
"traefik.http.middlewares.Middleware7.forwardauth.tls.caoptional": "true",
|
||||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.cert": "foobar",
|
"traefik.http.middlewares.Middleware7.forwardauth.tls.cert": "foobar",
|
||||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.insecureskipverify": "true",
|
"traefik.http.middlewares.Middleware7.forwardauth.tls.insecureskipverify": "true",
|
||||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.key": "foobar",
|
"traefik.http.middlewares.Middleware7.forwardauth.tls.key": "foobar",
|
||||||
"traefik.http.middlewares.Middleware7.forwardauth.trustforwardheader": "true",
|
"traefik.http.middlewares.Middleware7.forwardauth.trustforwardheader": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowcredentials": "true",
|
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowcredentials": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.allowedhosts": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware8.headers.allowedhosts": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowheaders": "X-foobar, X-fiibar",
|
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowheaders": "X-foobar, X-fiibar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowmethods": "GET, PUT",
|
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowmethods": "GET, PUT",
|
||||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolalloworigin": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.accesscontrolalloworigin": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolexposeheaders": "X-foobar, X-fiibar",
|
"traefik.http.middlewares.Middleware8.headers.accesscontrolexposeheaders": "X-foobar, X-fiibar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolmaxage": "200",
|
"traefik.http.middlewares.Middleware8.headers.accesscontrolmaxage": "200",
|
||||||
"traefik.http.middlewares.Middleware8.headers.addvaryheader": "true",
|
"traefik.http.middlewares.Middleware8.headers.addvaryheader": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.browserxssfilter": "true",
|
"traefik.http.middlewares.Middleware8.headers.browserxssfilter": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicy": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicy": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.contenttypenosniff": "true",
|
"traefik.http.middlewares.Middleware8.headers.contenttypenosniff": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.custombrowserxssvalue": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.custombrowserxssvalue": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.customframeoptionsvalue": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.customframeoptionsvalue": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.customrequestheaders.name0": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.customrequestheaders.name0": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.customrequestheaders.name1": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.customrequestheaders.name1": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.customresponseheaders.name0": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.customresponseheaders.name0": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.customresponseheaders.name1": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.customresponseheaders.name1": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.forcestsheader": "true",
|
"traefik.http.middlewares.Middleware8.headers.forcestsheader": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.framedeny": "true",
|
"traefik.http.middlewares.Middleware8.headers.framedeny": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.hostsproxyheaders": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware8.headers.hostsproxyheaders": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.isdevelopment": "true",
|
"traefik.http.middlewares.Middleware8.headers.isdevelopment": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.publickey": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.publickey": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.referrerpolicy": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.referrerpolicy": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.featurepolicy": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.featurepolicy": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.sslforcehost": "true",
|
"traefik.http.middlewares.Middleware8.headers.sslforcehost": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.sslhost": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.sslhost": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name0": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name0": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name1": "foobar",
|
"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name1": "foobar",
|
||||||
"traefik.http.middlewares.Middleware8.headers.sslredirect": "true",
|
"traefik.http.middlewares.Middleware8.headers.sslredirect": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.ssltemporaryredirect": "true",
|
"traefik.http.middlewares.Middleware8.headers.ssltemporaryredirect": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.stsincludesubdomains": "true",
|
"traefik.http.middlewares.Middleware8.headers.stsincludesubdomains": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.stspreload": "true",
|
"traefik.http.middlewares.Middleware8.headers.stspreload": "true",
|
||||||
"traefik.http.middlewares.Middleware8.headers.stsseconds": "42",
|
"traefik.http.middlewares.Middleware8.headers.stsseconds": "42",
|
||||||
"traefik.http.middlewares.Middleware9.ipwhitelist.ipstrategy.depth": "42",
|
"traefik.http.middlewares.Middleware9.ipwhitelist.ipstrategy.depth": "42",
|
||||||
"traefik.http.middlewares.Middleware9.ipwhitelist.ipstrategy.excludedips": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware9.ipwhitelist.ipstrategy.excludedips": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware9.ipwhitelist.sourcerange": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware9.ipwhitelist.sourcerange": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware10.maxconn.amount": "42",
|
"traefik.http.middlewares.Middleware10.inflightreq.amount": "42",
|
||||||
"traefik.http.middlewares.Middleware10.maxconn.extractorfunc": "foobar",
|
"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.ipstrategy.depth": "42",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.notafter": "true",
|
"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.ipstrategy.excludedips": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.notbefore": "true",
|
"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.requestheadername": "foobar",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.sans": "true",
|
"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.requesthost": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.commonname": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.notafter": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.country": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.notbefore": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.domaincomponent": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.sans": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.locality": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.commonname": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.organization": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.country": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.province": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.domaincomponent": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.serialnumber": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.locality": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.commonname": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.organization": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.country": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.province": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.domaincomponent": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.serialnumber": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.locality": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.commonname": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.organization": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.country": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.province": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.domaincomponent": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.serialnumber": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.locality": "true",
|
||||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.pem": "true",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.organization": "true",
|
||||||
// TODO: disable temporarily (rateLimit)
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.province": "true",
|
||||||
// "traefik.http.middlewares.Middleware12.ratelimit.extractorfunc": "foobar",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.serialnumber": "true",
|
||||||
// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate0.average": "42",
|
"traefik.http.middlewares.Middleware11.passtlsclientcert.pem": "true",
|
||||||
// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate0.burst": "42",
|
"traefik.http.middlewares.Middleware12.ratelimit.average": "42",
|
||||||
// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate0.period": "42",
|
"traefik.http.middlewares.Middleware12.ratelimit.burst": "42",
|
||||||
// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate1.average": "42",
|
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.requestheadername": "foobar",
|
||||||
// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate1.burst": "42",
|
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.requesthost": "true",
|
||||||
// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate1.period": "42",
|
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.ipstrategy.depth": "42",
|
||||||
"traefik.http.middlewares.Middleware13.redirectregex.permanent": "true",
|
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.ipstrategy.excludedips": "foobar, foobar",
|
||||||
"traefik.http.middlewares.Middleware13.redirectregex.regex": "foobar",
|
"traefik.http.middlewares.Middleware13.redirectregex.permanent": "true",
|
||||||
"traefik.http.middlewares.Middleware13.redirectregex.replacement": "foobar",
|
"traefik.http.middlewares.Middleware13.redirectregex.regex": "foobar",
|
||||||
"traefik.http.middlewares.Middleware13b.redirectscheme.scheme": "https",
|
"traefik.http.middlewares.Middleware13.redirectregex.replacement": "foobar",
|
||||||
"traefik.http.middlewares.Middleware13b.redirectscheme.port": "80",
|
"traefik.http.middlewares.Middleware13b.redirectscheme.scheme": "https",
|
||||||
"traefik.http.middlewares.Middleware13b.redirectscheme.permanent": "true",
|
"traefik.http.middlewares.Middleware13b.redirectscheme.port": "80",
|
||||||
"traefik.http.middlewares.Middleware14.replacepath.path": "foobar",
|
"traefik.http.middlewares.Middleware13b.redirectscheme.permanent": "true",
|
||||||
"traefik.http.middlewares.Middleware15.replacepathregex.regex": "foobar",
|
"traefik.http.middlewares.Middleware14.replacepath.path": "foobar",
|
||||||
"traefik.http.middlewares.Middleware15.replacepathregex.replacement": "foobar",
|
"traefik.http.middlewares.Middleware15.replacepathregex.regex": "foobar",
|
||||||
"traefik.http.middlewares.Middleware16.retry.attempts": "42",
|
"traefik.http.middlewares.Middleware15.replacepathregex.replacement": "foobar",
|
||||||
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware16.retry.attempts": "42",
|
||||||
"traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware19.compress": "true",
|
"traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
|
||||||
|
"traefik.http.middlewares.Middleware19.compress": "true",
|
||||||
|
|
||||||
"traefik.http.routers.Router0.entrypoints": "foobar, fiibar",
|
"traefik.http.routers.Router0.entrypoints": "foobar, fiibar",
|
||||||
"traefik.http.routers.Router0.middlewares": "foobar, fiibar",
|
"traefik.http.routers.Router0.middlewares": "foobar, fiibar",
|
||||||
|
@ -273,9 +274,16 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware10": {
|
"Middleware10": {
|
||||||
MaxConn: &dynamic.MaxConn{
|
InFlightReq: &dynamic.InFlightReq{
|
||||||
Amount: 42,
|
Amount: 42,
|
||||||
ExtractorFunc: "foobar",
|
SourceCriterion: &dynamic.SourceCriterion{
|
||||||
|
IPStrategy: &dynamic.IPStrategy{
|
||||||
|
Depth: 42,
|
||||||
|
ExcludedIPs: []string{"foobar", "fiibar"},
|
||||||
|
},
|
||||||
|
RequestHeaderName: "foobar",
|
||||||
|
RequestHost: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware11": {
|
"Middleware11": {
|
||||||
|
@ -306,24 +314,20 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: disable temporarily (rateLimit)
|
"Middleware12": {
|
||||||
// "Middleware12": {
|
RateLimit: &dynamic.RateLimit{
|
||||||
// RateLimit: &dynamic.RateLimit{
|
Average: 42,
|
||||||
// RateSet: map[string]*dynamic.Rate{
|
Burst: 42,
|
||||||
// "Rate0": {
|
SourceCriterion: &dynamic.SourceCriterion{
|
||||||
// Period: types.Duration(42 * time.Second),
|
IPStrategy: &dynamic.IPStrategy{
|
||||||
// Average: 42,
|
Depth: 42,
|
||||||
// Burst: 42,
|
ExcludedIPs: []string{"foobar", "foobar"},
|
||||||
// },
|
},
|
||||||
// "Rate1": {
|
RequestHeaderName: "foobar",
|
||||||
// Period: types.Duration(42 * time.Second),
|
RequestHost: true,
|
||||||
// Average: 42,
|
},
|
||||||
// Burst: 42,
|
},
|
||||||
// },
|
},
|
||||||
// },
|
|
||||||
// ExtractorFunc: "foobar",
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
"Middleware13": {
|
"Middleware13": {
|
||||||
RedirectRegex: &dynamic.RedirectRegex{
|
RedirectRegex: &dynamic.RedirectRegex{
|
||||||
Regex: "foobar",
|
Regex: "foobar",
|
||||||
|
@ -674,9 +678,16 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware10": {
|
"Middleware10": {
|
||||||
MaxConn: &dynamic.MaxConn{
|
InFlightReq: &dynamic.InFlightReq{
|
||||||
Amount: 42,
|
Amount: 42,
|
||||||
ExtractorFunc: "foobar",
|
SourceCriterion: &dynamic.SourceCriterion{
|
||||||
|
IPStrategy: &dynamic.IPStrategy{
|
||||||
|
Depth: 42,
|
||||||
|
ExcludedIPs: []string{"foobar", "fiibar"},
|
||||||
|
},
|
||||||
|
RequestHeaderName: "foobar",
|
||||||
|
RequestHost: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware11": {
|
"Middleware11": {
|
||||||
|
@ -706,24 +717,20 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: disable temporarily (rateLimit)
|
"Middleware12": {
|
||||||
// "Middleware12": {
|
RateLimit: &dynamic.RateLimit{
|
||||||
// RateLimit: &dynamic.RateLimit{
|
Average: 42,
|
||||||
// RateSet: map[string]*dynamic.Rate{
|
Burst: 42,
|
||||||
// "Rate0": {
|
SourceCriterion: &dynamic.SourceCriterion{
|
||||||
// Period: types.Duration(42 * time.Nanosecond),
|
IPStrategy: &dynamic.IPStrategy{
|
||||||
// Average: 42,
|
Depth: 42,
|
||||||
// Burst: 42,
|
ExcludedIPs: []string{"foobar", "foobar"},
|
||||||
// },
|
},
|
||||||
// "Rate1": {
|
RequestHeaderName: "foobar",
|
||||||
// Period: types.Duration(42 * time.Nanosecond),
|
RequestHost: true,
|
||||||
// Average: 42,
|
},
|
||||||
// Burst: 42,
|
},
|
||||||
// },
|
},
|
||||||
// },
|
|
||||||
// ExtractorFunc: "foobar",
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
"Middleware13": {
|
"Middleware13": {
|
||||||
RedirectRegex: &dynamic.RedirectRegex{
|
RedirectRegex: &dynamic.RedirectRegex{
|
||||||
Regex: "foobar",
|
Regex: "foobar",
|
||||||
|
@ -975,112 +982,113 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected := map[string]string{
|
expected := map[string]string{
|
||||||
"traefik.HTTP.Middlewares.Middleware0.AddPrefix.Prefix": "foobar",
|
"traefik.HTTP.Middlewares.Middleware0.AddPrefix.Prefix": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.HeaderField": "foobar",
|
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.HeaderField": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.Realm": "foobar",
|
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.Realm": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.RemoveHeader": "true",
|
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.RemoveHeader": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.Users": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.Users": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.UsersFile": "foobar",
|
"traefik.HTTP.Middlewares.Middleware1.BasicAuth.UsersFile": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware2.Buffering.MaxRequestBodyBytes": "42",
|
"traefik.HTTP.Middlewares.Middleware2.Buffering.MaxRequestBodyBytes": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware2.Buffering.MaxResponseBodyBytes": "42",
|
"traefik.HTTP.Middlewares.Middleware2.Buffering.MaxResponseBodyBytes": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware2.Buffering.MemRequestBodyBytes": "42",
|
"traefik.HTTP.Middlewares.Middleware2.Buffering.MemRequestBodyBytes": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware2.Buffering.MemResponseBodyBytes": "42",
|
"traefik.HTTP.Middlewares.Middleware2.Buffering.MemResponseBodyBytes": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware2.Buffering.RetryExpression": "foobar",
|
"traefik.HTTP.Middlewares.Middleware2.Buffering.RetryExpression": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware3.Chain.Middlewares": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware3.Chain.Middlewares": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.Expression": "foobar",
|
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.Expression": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField": "foobar",
|
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm": "foobar",
|
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader": "true",
|
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Users": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Users": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.UsersFile": "foobar",
|
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.UsersFile": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware6.Errors.Query": "foobar",
|
"traefik.HTTP.Middlewares.Middleware6.Errors.Query": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware6.Errors.Service": "foobar",
|
"traefik.HTTP.Middlewares.Middleware6.Errors.Service": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware6.Errors.Status": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware6.Errors.Status": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.Address": "foobar",
|
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.Address": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthResponseHeaders": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthResponseHeaders": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CA": "foobar",
|
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CA": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CAOptional": "true",
|
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CAOptional": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Cert": "foobar",
|
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Cert": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.InsecureSkipVerify": "true",
|
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.InsecureSkipVerify": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key": "foobar",
|
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader": "true",
|
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowCredentials": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowCredentials": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowHeaders": "X-foobar, X-fiibar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowHeaders": "X-foobar, X-fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowMethods": "GET, PUT",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowMethods": "GET, PUT",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowOrigin": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowOrigin": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlExposeHeaders": "X-foobar, X-fiibar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlExposeHeaders": "X-foobar, X-fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlMaxAge": "200",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlMaxAge": "200",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AddVaryHeader": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.AddVaryHeader": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AllowedHosts": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.AllowedHosts": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.BrowserXSSFilter": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.BrowserXSSFilter": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicy": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicy": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentTypeNosniff": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentTypeNosniff": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomBrowserXSSValue": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomBrowserXSSValue": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomFrameOptionsValue": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomFrameOptionsValue": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomRequestHeaders.name0": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomRequestHeaders.name0": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomRequestHeaders.name1": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomRequestHeaders.name1": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomResponseHeaders.name0": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomResponseHeaders.name0": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomResponseHeaders.name1": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomResponseHeaders.name1": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.ForceSTSHeader": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.ForceSTSHeader": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.FrameDeny": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.FrameDeny": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.HostsProxyHeaders": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.HostsProxyHeaders": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.IsDevelopment": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.IsDevelopment": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.PublicKey": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.PublicKey": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.ReferrerPolicy": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.ReferrerPolicy": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.FeaturePolicy": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.FeaturePolicy": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLForceHost": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLForceHost": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLHost": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLHost": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name0": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name0": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name1": "foobar",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name1": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLRedirect": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLRedirect": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLTemporaryRedirect": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.SSLTemporaryRedirect": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.STSIncludeSubdomains": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.STSIncludeSubdomains": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.STSPreload": "true",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.STSPreload": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds": "42",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.IPStrategy.Depth": "42",
|
"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.IPStrategy.Depth": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.IPStrategy.ExcludedIPs": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.IPStrategy.ExcludedIPs": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.SourceRange": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.SourceRange": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware10.MaxConn.Amount": "42",
|
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.Amount": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware10.MaxConn.ExtractorFunc": "foobar",
|
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.Depth": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotAfter": "true",
|
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.ExcludedIPs": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotBefore": "true",
|
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.RequestHeaderName": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Sans": "true",
|
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.RequestHost": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Country": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotAfter": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Province": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotBefore": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Locality": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Sans": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Organization": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Country": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.CommonName": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Province": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.SerialNumber": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Locality": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.DomainComponent": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Organization": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Country": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.CommonName": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Province": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.SerialNumber": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Locality": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.DomainComponent": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Organization": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Country": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.CommonName": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Province": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.SerialNumber": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Locality": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.DomainComponent": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Organization": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.PEM": "true",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.CommonName": "true",
|
||||||
// TODO: disable temporarily (rateLimit)
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.SerialNumber": "true",
|
||||||
// "traefik.HTTP.Middlewares.Middleware12.RateLimit.ExtractorFunc": "foobar",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.DomainComponent": "true",
|
||||||
// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate0.Average": "42",
|
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.PEM": "true",
|
||||||
// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate0.Burst": "42",
|
"traefik.HTTP.Middlewares.Middleware12.RateLimit.Average": "42",
|
||||||
// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate0.Period": "42",
|
"traefik.HTTP.Middlewares.Middleware12.RateLimit.Burst": "42",
|
||||||
// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Average": "42",
|
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.RequestHeaderName": "foobar",
|
||||||
// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Burst": "42",
|
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.RequestHost": "true",
|
||||||
// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Period": "42",
|
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.IPStrategy.Depth": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Regex": "foobar",
|
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.IPStrategy.ExcludedIPs": "foobar, foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Replacement": "foobar",
|
"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Regex": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Permanent": "true",
|
"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Replacement": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Scheme": "https",
|
"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Permanent": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Port": "80",
|
"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Scheme": "https",
|
||||||
"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Permanent": "true",
|
"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Port": "80",
|
||||||
"traefik.HTTP.Middlewares.Middleware14.ReplacePath.Path": "foobar",
|
"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Permanent": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Regex": "foobar",
|
"traefik.HTTP.Middlewares.Middleware14.ReplacePath.Path": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Replacement": "foobar",
|
"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Regex": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware16.Retry.Attempts": "42",
|
"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Replacement": "foobar",
|
||||||
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware16.Retry.Attempts": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware19.Compress": "true",
|
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
||||||
|
"traefik.HTTP.Middlewares.Middleware19.Compress": "true",
|
||||||
|
|
||||||
"traefik.HTTP.Routers.Router0.EntryPoints": "foobar, fiibar",
|
"traefik.HTTP.Routers.Router0.EntryPoints": "foobar, fiibar",
|
||||||
"traefik.HTTP.Routers.Router0.Middlewares": "foobar, fiibar",
|
"traefik.HTTP.Routers.Router0.Middlewares": "foobar, fiibar",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ip
|
package ip
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -17,9 +18,13 @@ type Strategy interface {
|
||||||
// RemoteAddrStrategy a strategy that always return the remote address
|
// RemoteAddrStrategy a strategy that always return the remote address
|
||||||
type RemoteAddrStrategy struct{}
|
type RemoteAddrStrategy struct{}
|
||||||
|
|
||||||
// GetIP return the selected IP
|
// GetIP returns the selected IP
|
||||||
func (s *RemoteAddrStrategy) GetIP(req *http.Request) string {
|
func (s *RemoteAddrStrategy) GetIP(req *http.Request) string {
|
||||||
return req.RemoteAddr
|
ip, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return req.RemoteAddr
|
||||||
|
}
|
||||||
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
// DepthStrategy a strategy based on the depth inside the X-Forwarded-For from right to left
|
// DepthStrategy a strategy based on the depth inside the X-Forwarded-For from right to left
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestRemoteAddrStrategy_GetIP(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "Use RemoteAddr",
|
desc: "Use RemoteAddr",
|
||||||
expected: "192.0.2.1:1234",
|
expected: "192.0.2.1",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
49
pkg/middlewares/extractor.go
Normal file
49
pkg/middlewares/extractor.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/v2/pkg/log"
|
||||||
|
"github.com/vulcand/oxy/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetSourceExtractor returns the SourceExtractor function corresponding to the given sourceMatcher.
|
||||||
|
// It defaults to a RemoteAddrStrategy IPStrategy if need be.
|
||||||
|
func GetSourceExtractor(ctx context.Context, sourceMatcher *dynamic.SourceCriterion) (utils.SourceExtractor, error) {
|
||||||
|
if sourceMatcher == nil ||
|
||||||
|
sourceMatcher.IPStrategy == nil &&
|
||||||
|
sourceMatcher.RequestHeaderName == "" && !sourceMatcher.RequestHost {
|
||||||
|
sourceMatcher = &dynamic.SourceCriterion{
|
||||||
|
IPStrategy: &dynamic.IPStrategy{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
if sourceMatcher.IPStrategy != nil {
|
||||||
|
strategy, err := sourceMatcher.IPStrategy.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("Using IPStrategy")
|
||||||
|
return utils.ExtractorFunc(func(req *http.Request) (string, int64, error) {
|
||||||
|
return strategy.GetIP(req), 1, nil
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceMatcher.RequestHeaderName != "" {
|
||||||
|
logger.Debug("Using RequestHeaderName")
|
||||||
|
return utils.NewExtractor(fmt.Sprintf("request.header.%s", sourceMatcher.RequestHeaderName))
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceMatcher.RequestHost {
|
||||||
|
logger.Debug("Using RequestHost")
|
||||||
|
return utils.NewExtractor("request.host")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("no SourceCriterion criterion defined")
|
||||||
|
}
|
57
pkg/middlewares/inflightreq/inflight_req.go
Normal file
57
pkg/middlewares/inflightreq/inflight_req.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package inflightreq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/v2/pkg/log"
|
||||||
|
"github.com/containous/traefik/v2/pkg/middlewares"
|
||||||
|
"github.com/containous/traefik/v2/pkg/tracing"
|
||||||
|
"github.com/opentracing/opentracing-go/ext"
|
||||||
|
"github.com/vulcand/oxy/connlimit"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeName = "InFlightReq"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inFlightReq struct {
|
||||||
|
handler http.Handler
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a max request middleware.
|
||||||
|
func New(ctx context.Context, next http.Handler, config dynamic.InFlightReq, name string) (http.Handler, error) {
|
||||||
|
ctxLog := log.With(ctx, log.Str(log.MiddlewareName, name), log.Str(log.MiddlewareType, typeName))
|
||||||
|
log.FromContext(ctxLog).Debug("Creating middleware")
|
||||||
|
|
||||||
|
if config.SourceCriterion == nil ||
|
||||||
|
config.SourceCriterion.IPStrategy == nil &&
|
||||||
|
config.SourceCriterion.RequestHeaderName == "" && !config.SourceCriterion.RequestHost {
|
||||||
|
config.SourceCriterion = &dynamic.SourceCriterion{
|
||||||
|
RequestHost: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMatcher, err := middlewares.GetSourceExtractor(ctxLog, config.SourceCriterion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating requests limiter: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler, err := connlimit.New(next, sourceMatcher, config.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating connection limit: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &inFlightReq{handler: handler, name: name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *inFlightReq) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
|
return i.name, tracing.SpanKindNoneEnum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *inFlightReq) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
i.handler.ServeHTTP(rw, req)
|
||||||
|
}
|
|
@ -1,48 +0,0 @@
|
||||||
package maxconnection
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares"
|
|
||||||
"github.com/containous/traefik/v2/pkg/tracing"
|
|
||||||
"github.com/opentracing/opentracing-go/ext"
|
|
||||||
"github.com/vulcand/oxy/connlimit"
|
|
||||||
"github.com/vulcand/oxy/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
typeName = "MaxConnection"
|
|
||||||
)
|
|
||||||
|
|
||||||
type maxConnection struct {
|
|
||||||
handler http.Handler
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a max connection middleware.
|
|
||||||
func New(ctx context.Context, next http.Handler, maxConns dynamic.MaxConn, name string) (http.Handler, error) {
|
|
||||||
middlewares.GetLogger(ctx, name, typeName).Debug("Creating middleware")
|
|
||||||
|
|
||||||
extractFunc, err := utils.NewExtractor(maxConns.ExtractorFunc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error creating connection limit: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
handler, err := connlimit.New(next, extractFunc, maxConns.Amount)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error creating connection limit: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &maxConnection{handler: handler, name: name}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *maxConnection) GetTracingInformation() (string, ext.SpanKindEnum) {
|
|
||||||
return mc.name, tracing.SpanKindNoneEnum
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *maxConnection) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
mc.handler.ServeHTTP(rw, req)
|
|
||||||
}
|
|
|
@ -1,54 +1,146 @@
|
||||||
|
// Package ratelimiter implements a rate limiting and traffic shaping middleware with a set of token buckets.
|
||||||
package ratelimiter
|
package ratelimiter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/v2/pkg/log"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares"
|
"github.com/containous/traefik/v2/pkg/middlewares"
|
||||||
"github.com/containous/traefik/v2/pkg/tracing"
|
"github.com/containous/traefik/v2/pkg/tracing"
|
||||||
|
"github.com/mailgun/ttlmap"
|
||||||
"github.com/opentracing/opentracing-go/ext"
|
"github.com/opentracing/opentracing-go/ext"
|
||||||
"github.com/vulcand/oxy/ratelimit"
|
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
typeName = "RateLimiterType"
|
typeName = "RateLimiterType"
|
||||||
|
maxSources = 65536
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// rateLimiter implements rate limiting and traffic shaping with a set of token buckets;
|
||||||
|
// one for each traffic source. The same parameters are applied to all the buckets.
|
||||||
type rateLimiter struct {
|
type rateLimiter struct {
|
||||||
handler http.Handler
|
name string
|
||||||
name string
|
rate rate.Limit // reqs/s
|
||||||
|
burst int64
|
||||||
|
// maxDelay is the maximum duration we're willing to wait for a bucket reservation to become effective, in nanoseconds.
|
||||||
|
// For now it is somewhat arbitrarily set to 1/rate.
|
||||||
|
maxDelay time.Duration
|
||||||
|
sourceMatcher utils.SourceExtractor
|
||||||
|
next http.Handler
|
||||||
|
|
||||||
|
bucketsMu sync.Mutex
|
||||||
|
buckets *ttlmap.TtlMap // actual buckets, keyed by source.
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates rate limiter middleware.
|
// New returns a rate limiter middleware.
|
||||||
func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name string) (http.Handler, error) {
|
func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name string) (http.Handler, error) {
|
||||||
middlewares.GetLogger(ctx, name, typeName).Debug("Creating middleware")
|
ctxLog := log.With(ctx, log.Str(log.MiddlewareName, name), log.Str(log.MiddlewareType, typeName))
|
||||||
|
log.FromContext(ctxLog).Debug("Creating middleware")
|
||||||
|
|
||||||
extractFunc, err := utils.NewExtractor(config.ExtractorFunc)
|
if config.SourceCriterion == nil ||
|
||||||
if err != nil {
|
config.SourceCriterion.IPStrategy == nil &&
|
||||||
return nil, err
|
config.SourceCriterion.RequestHeaderName == "" && !config.SourceCriterion.RequestHost {
|
||||||
}
|
config.SourceCriterion = &dynamic.SourceCriterion{
|
||||||
|
IPStrategy: &dynamic.IPStrategy{},
|
||||||
rateSet := ratelimit.NewRateSet()
|
|
||||||
for _, rate := range config.RateSet {
|
|
||||||
if err = rateSet.Add(time.Duration(rate.Period), rate.Average, rate.Burst); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rl, err := ratelimit.New(next, extractFunc, rateSet)
|
sourceMatcher, err := middlewares.GetSourceExtractor(ctxLog, config.SourceCriterion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rateLimiter{handler: rl, name: name}, nil
|
|
||||||
|
buckets, err := ttlmap.NewMap(maxSources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
burst := config.Burst
|
||||||
|
if burst <= 0 {
|
||||||
|
burst = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logically, we should set maxDelay to ~infinity when config.Average == 0 (because it means to rate limiting),
|
||||||
|
// but since the reservation will give us a delay = 0 anyway in this case, we're good even with any maxDelay >= 0.
|
||||||
|
var maxDelay time.Duration
|
||||||
|
if config.Average != 0 {
|
||||||
|
maxDelay = time.Second / time.Duration(config.Average*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rateLimiter{
|
||||||
|
name: name,
|
||||||
|
rate: rate.Limit(config.Average),
|
||||||
|
burst: burst,
|
||||||
|
maxDelay: maxDelay,
|
||||||
|
next: next,
|
||||||
|
sourceMatcher: sourceMatcher,
|
||||||
|
buckets: buckets,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rateLimiter) GetTracingInformation() (string, ext.SpanKindEnum) {
|
func (rl *rateLimiter) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
return r.name, tracing.SpanKindNoneEnum
|
return rl.name, tracing.SpanKindNoneEnum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (rl *rateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
r.handler.ServeHTTP(rw, req)
|
logger := middlewares.GetLogger(r.Context(), rl.name, typeName)
|
||||||
|
|
||||||
|
source, amount, err := rl.sourceMatcher.Extract(r)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("could not extract source of request: %v", err)
|
||||||
|
http.Error(w, "could not extract source of request", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if amount != 1 {
|
||||||
|
logger.Infof("ignoring token bucket amount > 1: %d", amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.bucketsMu.Lock()
|
||||||
|
defer rl.bucketsMu.Unlock()
|
||||||
|
|
||||||
|
var bucket *rate.Limiter
|
||||||
|
if rlSource, exists := rl.buckets.Get(source); exists {
|
||||||
|
bucket = rlSource.(*rate.Limiter)
|
||||||
|
} else {
|
||||||
|
bucket = rate.NewLimiter(rl.rate, int(rl.burst))
|
||||||
|
if err := rl.buckets.Set(source, bucket, int(rl.maxDelay)*10+1); err != nil {
|
||||||
|
logger.Errorf("could not insert bucket: %v", err)
|
||||||
|
http.Error(w, "could not insert bucket", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res := bucket.Reserve()
|
||||||
|
if !res.OK() {
|
||||||
|
http.Error(w, "No bursty traffic allowed", http.StatusTooManyRequests)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := res.Delay()
|
||||||
|
if delay > rl.maxDelay {
|
||||||
|
res.Cancel()
|
||||||
|
rl.serveDelayError(w, r, delay)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(delay)
|
||||||
|
rl.next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *rateLimiter) serveDelayError(w http.ResponseWriter, r *http.Request, delay time.Duration) {
|
||||||
|
w.Header().Set("Retry-After", fmt.Sprintf("%.0f", delay.Seconds()))
|
||||||
|
w.Header().Set("X-Retry-In", delay.String())
|
||||||
|
w.WriteHeader(http.StatusTooManyRequests)
|
||||||
|
|
||||||
|
if _, err := w.Write([]byte(http.StatusText(http.StatusTooManyRequests))); err != nil {
|
||||||
|
middlewares.GetLogger(r.Context(), rl.name, typeName).Errorf("could not serve 429: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
160
pkg/middlewares/ratelimiter/rate_limiter_test.go
Normal file
160
pkg/middlewares/ratelimiter/rate_limiter_test.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package ratelimiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
|
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/vulcand/oxy/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewRateLimiter(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
config dynamic.RateLimit
|
||||||
|
expectedMaxDelay time.Duration
|
||||||
|
expectedSourceIP string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "maxDelay computation",
|
||||||
|
config: dynamic.RateLimit{
|
||||||
|
Average: 200,
|
||||||
|
Burst: 10,
|
||||||
|
},
|
||||||
|
expectedMaxDelay: 2500 * time.Microsecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "default SourceMatcher is remote address ip strategy",
|
||||||
|
config: dynamic.RateLimit{
|
||||||
|
Average: 200,
|
||||||
|
Burst: 10,
|
||||||
|
},
|
||||||
|
expectedSourceIP: "127.0.0.1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||||
|
|
||||||
|
h, err := New(context.Background(), next, test.config, "rate-limiter")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rtl, _ := h.(*rateLimiter)
|
||||||
|
if test.expectedMaxDelay != 0 {
|
||||||
|
assert.Equal(t, test.expectedMaxDelay, rtl.maxDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.expectedSourceIP != "" {
|
||||||
|
extractor, ok := rtl.sourceMatcher.(utils.ExtractorFunc)
|
||||||
|
require.True(t, ok, "Not an ExtractorFunc")
|
||||||
|
|
||||||
|
req := http.Request{
|
||||||
|
RemoteAddr: fmt.Sprintf("%s:1234", test.expectedSourceIP),
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, _, err := extractor(&req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expectedSourceIP, ip)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRateLimit(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
config dynamic.RateLimit
|
||||||
|
reqCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Average is respected",
|
||||||
|
config: dynamic.RateLimit{
|
||||||
|
Average: 100,
|
||||||
|
Burst: 1,
|
||||||
|
},
|
||||||
|
reqCount: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Burst is taken into account",
|
||||||
|
config: dynamic.RateLimit{
|
||||||
|
Average: 100,
|
||||||
|
Burst: 200,
|
||||||
|
},
|
||||||
|
reqCount: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Zero average ==> no rate limiting",
|
||||||
|
config: dynamic.RateLimit{
|
||||||
|
Average: 0,
|
||||||
|
Burst: 1,
|
||||||
|
},
|
||||||
|
reqCount: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
reqCount := 0
|
||||||
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqCount++
|
||||||
|
})
|
||||||
|
|
||||||
|
h, err := New(context.Background(), next, test.config, "rate-limiter")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
for {
|
||||||
|
if reqCount >= test.reqCount {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||||
|
req.RemoteAddr = "127.0.0.1:1234"
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.ServeHTTP(w, req)
|
||||||
|
// TODO(mpl): predict and count the 200 VS the 429?
|
||||||
|
}
|
||||||
|
|
||||||
|
stop := time.Now()
|
||||||
|
elapsed := stop.Sub(start)
|
||||||
|
if test.config.Average == 0 {
|
||||||
|
if elapsed > time.Millisecond {
|
||||||
|
t.Fatalf("rate should not have been limited, but: %d requests in %v", reqCount, elapsed)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume allowed burst is initially consumed in an infinitesimal period of time
|
||||||
|
var expectedDuration time.Duration
|
||||||
|
if test.config.Average != 0 {
|
||||||
|
expectedDuration = time.Duration((int64(test.reqCount)-test.config.Burst+1)/test.config.Average) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for a 2% leeway
|
||||||
|
minDuration := expectedDuration * 98 / 100
|
||||||
|
maxDuration := expectedDuration * 102 / 100
|
||||||
|
|
||||||
|
if elapsed < minDuration {
|
||||||
|
t.Fatalf("rate was faster than expected: %d requests in %v", reqCount, elapsed)
|
||||||
|
}
|
||||||
|
if elapsed > maxDuration {
|
||||||
|
t.Fatalf("rate was slower than expected: %d requests in %v", reqCount, elapsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1007,13 +1007,13 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "one container with MaxConn in label (default value)",
|
desc: "one container with InFlightReq in label (default value)",
|
||||||
containers: []dockerData{
|
containers: []dockerData{
|
||||||
{
|
{
|
||||||
ServiceName: "Test",
|
ServiceName: "Test",
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
|
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
|
||||||
},
|
},
|
||||||
NetworkSettings: networkSettings{
|
NetworkSettings: networkSettings{
|
||||||
Ports: nat.PortMap{
|
Ports: nat.PortMap{
|
||||||
|
@ -1054,9 +1054,11 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
Middlewares: map[string]*dynamic.Middleware{
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
"Middleware1": {
|
"Middleware1": {
|
||||||
MaxConn: &dynamic.MaxConn{
|
InFlightReq: &dynamic.InFlightReq{
|
||||||
Amount: 42,
|
Amount: 42,
|
||||||
ExtractorFunc: "request.host",
|
SourceCriterion: &dynamic.SourceCriterion{
|
||||||
|
RequestHost: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1071,7 +1073,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
ServiceName: "Test",
|
ServiceName: "Test",
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
|
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
|
||||||
},
|
},
|
||||||
NetworkSettings: networkSettings{
|
NetworkSettings: networkSettings{
|
||||||
Ports: nat.PortMap{
|
Ports: nat.PortMap{
|
||||||
|
@ -1090,7 +1092,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
ServiceName: "Test",
|
ServiceName: "Test",
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
|
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
|
||||||
},
|
},
|
||||||
NetworkSettings: networkSettings{
|
NetworkSettings: networkSettings{
|
||||||
Ports: nat.PortMap{
|
Ports: nat.PortMap{
|
||||||
|
@ -1119,9 +1121,11 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
Middlewares: map[string]*dynamic.Middleware{
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
"Middleware1": {
|
"Middleware1": {
|
||||||
MaxConn: &dynamic.MaxConn{
|
InFlightReq: &dynamic.InFlightReq{
|
||||||
Amount: 42,
|
Amount: 42,
|
||||||
ExtractorFunc: "request.host",
|
SourceCriterion: &dynamic.SourceCriterion{
|
||||||
|
RequestHost: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1151,7 +1155,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
ServiceName: "Test",
|
ServiceName: "Test",
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
|
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
|
||||||
},
|
},
|
||||||
NetworkSettings: networkSettings{
|
NetworkSettings: networkSettings{
|
||||||
Ports: nat.PortMap{
|
Ports: nat.PortMap{
|
||||||
|
@ -1170,7 +1174,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
ServiceName: "Test",
|
ServiceName: "Test",
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.http.middlewares.Middleware1.maxconn.amount": "41",
|
"traefik.http.middlewares.Middleware1.inflightreq.amount": "41",
|
||||||
},
|
},
|
||||||
NetworkSettings: networkSettings{
|
NetworkSettings: networkSettings{
|
||||||
Ports: nat.PortMap{
|
Ports: nat.PortMap{
|
||||||
|
@ -1224,7 +1228,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
ServiceName: "Test",
|
ServiceName: "Test",
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
|
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
|
||||||
},
|
},
|
||||||
NetworkSettings: networkSettings{
|
NetworkSettings: networkSettings{
|
||||||
Ports: nat.PortMap{
|
Ports: nat.PortMap{
|
||||||
|
@ -1243,7 +1247,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
ServiceName: "Test",
|
ServiceName: "Test",
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.http.middlewares.Middleware1.maxconn.amount": "41",
|
"traefik.http.middlewares.Middleware1.inflightreq.amount": "41",
|
||||||
},
|
},
|
||||||
NetworkSettings: networkSettings{
|
NetworkSettings: networkSettings{
|
||||||
Ports: nat.PortMap{
|
Ports: nat.PortMap{
|
||||||
|
@ -1262,7 +1266,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
ServiceName: "Test",
|
ServiceName: "Test",
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.http.middlewares.Middleware1.maxconn.amount": "40",
|
"traefik.http.middlewares.Middleware1.inflightreq.amount": "40",
|
||||||
},
|
},
|
||||||
NetworkSettings: networkSettings{
|
NetworkSettings: networkSettings{
|
||||||
Ports: nat.PortMap{
|
Ports: nat.PortMap{
|
||||||
|
@ -1809,7 +1813,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
ServiceName: "Test",
|
ServiceName: "Test",
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
|
"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
|
||||||
},
|
},
|
||||||
NetworkSettings: networkSettings{
|
NetworkSettings: networkSettings{
|
||||||
Ports: nat.PortMap{},
|
Ports: nat.PortMap{},
|
||||||
|
|
|
@ -595,13 +595,13 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
appID("/app"),
|
appID("/app"),
|
||||||
appPorts(80, 81),
|
appPorts(80, 81),
|
||||||
withTasks(localhostTask(taskPorts(80, 81))),
|
withTasks(localhostTask(taskPorts(80, 81))),
|
||||||
withLabel("traefik.http.middlewares.Middleware1.maxconn.amount", "42"),
|
withLabel("traefik.http.middlewares.Middleware1.inflightreq.amount", "42"),
|
||||||
),
|
),
|
||||||
application(
|
application(
|
||||||
appID("/app2"),
|
appID("/app2"),
|
||||||
appPorts(80, 81),
|
appPorts(80, 81),
|
||||||
withTasks(localhostTask(taskPorts(80, 81))),
|
withTasks(localhostTask(taskPorts(80, 81))),
|
||||||
withLabel("traefik.http.middlewares.Middleware1.maxconn.amount", "42"),
|
withLabel("traefik.http.middlewares.Middleware1.inflightreq.amount", "42"),
|
||||||
)),
|
)),
|
||||||
expected: &dynamic.Configuration{
|
expected: &dynamic.Configuration{
|
||||||
TCP: &dynamic.TCPConfiguration{
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
@ -621,9 +621,11 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
Middlewares: map[string]*dynamic.Middleware{
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
"Middleware1": {
|
"Middleware1": {
|
||||||
MaxConn: &dynamic.MaxConn{
|
InFlightReq: &dynamic.InFlightReq{
|
||||||
Amount: 42,
|
Amount: 42,
|
||||||
ExtractorFunc: "request.host",
|
SourceCriterion: &dynamic.SourceCriterion{
|
||||||
|
RequestHost: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -659,13 +661,13 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
appID("/app"),
|
appID("/app"),
|
||||||
appPorts(80, 81),
|
appPorts(80, 81),
|
||||||
withTasks(localhostTask(taskPorts(80, 81))),
|
withTasks(localhostTask(taskPorts(80, 81))),
|
||||||
withLabel("traefik.http.middlewares.Middleware1.maxconn.amount", "42"),
|
withLabel("traefik.http.middlewares.Middleware1.inflightreq.amount", "42"),
|
||||||
),
|
),
|
||||||
application(
|
application(
|
||||||
appID("/app2"),
|
appID("/app2"),
|
||||||
appPorts(80, 81),
|
appPorts(80, 81),
|
||||||
withTasks(localhostTask(taskPorts(80, 81))),
|
withTasks(localhostTask(taskPorts(80, 81))),
|
||||||
withLabel("traefik.http.middlewares.Middleware1.maxconn.amount", "41"),
|
withLabel("traefik.http.middlewares.Middleware1.inflightreq.amount", "41"),
|
||||||
)),
|
)),
|
||||||
expected: &dynamic.Configuration{
|
expected: &dynamic.Configuration{
|
||||||
TCP: &dynamic.TCPConfiguration{
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
|
|
@ -17,9 +17,10 @@ import (
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/compress"
|
"github.com/containous/traefik/v2/pkg/middlewares/compress"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/customerrors"
|
"github.com/containous/traefik/v2/pkg/middlewares/customerrors"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/headers"
|
"github.com/containous/traefik/v2/pkg/middlewares/headers"
|
||||||
|
"github.com/containous/traefik/v2/pkg/middlewares/inflightreq"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/ipwhitelist"
|
"github.com/containous/traefik/v2/pkg/middlewares/ipwhitelist"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/maxconnection"
|
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/passtlsclientcert"
|
"github.com/containous/traefik/v2/pkg/middlewares/passtlsclientcert"
|
||||||
|
"github.com/containous/traefik/v2/pkg/middlewares/ratelimiter"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/redirect"
|
"github.com/containous/traefik/v2/pkg/middlewares/redirect"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/replacepath"
|
"github.com/containous/traefik/v2/pkg/middlewares/replacepath"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/replacepathregex"
|
"github.com/containous/traefik/v2/pkg/middlewares/replacepathregex"
|
||||||
|
@ -122,7 +123,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffering
|
// Buffering
|
||||||
if config.Buffering != nil && config.MaxConn.Amount != 0 {
|
if config.Buffering != nil && config.InFlightReq.Amount != 0 {
|
||||||
if middleware != nil {
|
if middleware != nil {
|
||||||
return nil, badConf
|
return nil, badConf
|
||||||
}
|
}
|
||||||
|
@ -211,13 +212,13 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxConn
|
// InFlightReq
|
||||||
if config.MaxConn != nil && config.MaxConn.Amount != 0 {
|
if config.InFlightReq != nil && config.InFlightReq.Amount != 0 {
|
||||||
if middleware != nil {
|
if middleware != nil {
|
||||||
return nil, badConf
|
return nil, badConf
|
||||||
}
|
}
|
||||||
middleware = func(next http.Handler) (http.Handler, error) {
|
middleware = func(next http.Handler) (http.Handler, error) {
|
||||||
return maxconnection.New(ctx, next, *config.MaxConn, middlewareName)
|
return inflightreq.New(ctx, next, *config.InFlightReq, middlewareName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,16 +232,15 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: disable temporarily (rateLimit)
|
|
||||||
// RateLimit
|
// RateLimit
|
||||||
// if config.RateLimit != nil {
|
if config.RateLimit != nil {
|
||||||
// if middleware != nil {
|
if middleware != nil {
|
||||||
// return nil, badConf
|
return nil, badConf
|
||||||
// }
|
}
|
||||||
// middleware = func(next http.Handler) (http.Handler, error) {
|
middleware = func(next http.Handler) (http.Handler, error) {
|
||||||
// return ratelimiter.New(ctx, next, *config.RateLimit, middlewareName)
|
return ratelimiter.New(ctx, next, *config.RateLimit, middlewareName)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// RedirectRegex
|
// RedirectRegex
|
||||||
if config.RedirectRegex != nil {
|
if config.RedirectRegex != nil {
|
||||||
|
|
Loading…
Reference in a new issue