Add support for ipv6 subnet in ipStrategy
This commit is contained in:
parent
a398536688
commit
312ebb17ab
17 changed files with 544 additions and 12 deletions
|
@ -101,7 +101,7 @@ If none are set, the default is to use the `requestHost`.
|
|||
|
||||
#### `sourceCriterion.ipStrategy`
|
||||
|
||||
The `ipStrategy` option defines two parameters that configures how Traefik determines the client IP: `depth`, and `excludedIPs`.
|
||||
The `ipStrategy` option defines three parameters that configures how Traefik determines the client IP: `depth`, `excludedIPs` and `ipv6Subnet`.
|
||||
|
||||
!!! important "As a middleware, InFlightReq happens before the actual proxying to the backend takes place. In addition, the previous network hop only gets appended to `X-Forwarded-For` during the last stages of proxying, i.e. after it has already passed through the middleware. Therefore, during InFlightReq, as the previous network hop is not yet present in `X-Forwarded-For`, it cannot be used and/or relied upon."
|
||||
|
||||
|
@ -112,6 +112,9 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and select
|
|||
- If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP is empty.
|
||||
- `depth` is ignored if its value is less than or equal to 0.
|
||||
|
||||
If `ipStrategy.ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
|
||||
See [ipStrategy.ipv6Subnet](#ipstrategyipv6subnet) for more details.
|
||||
|
||||
!!! example "Example of Depth & X-Forwarded-For"
|
||||
|
||||
If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP is `"10.0.0.1"` (at depth 4) but the IP used as the criterion is `"12.0.0.1"` (`depth=2`).
|
||||
|
@ -218,6 +221,63 @@ http:
|
|||
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
```
|
||||
|
||||
##### `ipStrategy.ipv6Subnet`
|
||||
|
||||
This strategy applies to `Depth` and `RemoteAddr` strategy only.
|
||||
If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
|
||||
|
||||
This is useful for grouping IPv6 addresses into subnets to prevent bypassing this middleware by obtaining a new IPv6.
|
||||
|
||||
- `ipv6Subnet` is ignored if its value is outside of 0-128 interval
|
||||
|
||||
!!! example "Example of ipv6Subnet"
|
||||
|
||||
If `ipv6Subnet` is provided, the IP is transformed in the following way.
|
||||
|
||||
| `IP` | `ipv6Subnet` | clientIP |
|
||||
|---------------------------|--------------|-----------------------|
|
||||
| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` |
|
||||
| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` |
|
||||
| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` |
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.ipv6Subnet=64"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-inflightreq
|
||||
spec:
|
||||
inFlightReq:
|
||||
sourceCriterion:
|
||||
ipStrategy:
|
||||
ipv6Subnet: 64
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.ipv6Subnet=64"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
middlewares:
|
||||
test-inflightreq:
|
||||
inFlightReq:
|
||||
sourceCriterion:
|
||||
ipStrategy:
|
||||
ipv6Subnet: 64
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.middlewares]
|
||||
[http.middlewares.test-inflightreq.inflightreq]
|
||||
[http.middlewares.test-inflightreq.inFlightReq.sourceCriterion.ipStrategy]
|
||||
ipv6Subnet = 64
|
||||
```
|
||||
|
||||
#### `sourceCriterion.requestHeaderName`
|
||||
|
||||
Name of the header used to group incoming requests.
|
||||
|
|
|
@ -75,6 +75,9 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and take th
|
|||
- 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 less than or equal to 0.
|
||||
|
||||
If `ipStrategy.ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
|
||||
See [ipStrategy.ipv6Subnet](#ipstrategyipv6subnet) for more details.
|
||||
|
||||
!!! example "Examples of Depth & X-Forwarded-For"
|
||||
|
||||
If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP is `"10.0.0.1"` (at depth 4) but the IP used is `"12.0.0.1"` (`depth=2`).
|
||||
|
@ -204,3 +207,60 @@ http:
|
|||
[http.middlewares.test-ipallowlist.ipAllowList.ipStrategy]
|
||||
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
```
|
||||
|
||||
#### `ipStrategy.ipv6Subnet`
|
||||
|
||||
This strategy applies to `Depth` and `RemoteAddr` strategy only.
|
||||
If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
|
||||
|
||||
This is useful for grouping IPv6 addresses into subnets to prevent bypassing this middleware by obtaining a new IPv6.
|
||||
|
||||
- `ipv6Subnet` is ignored if its value is outside of 0-128 interval
|
||||
|
||||
!!! example "Example of ipv6Subnet"
|
||||
|
||||
If `ipv6Subnet` is provided, the IP is transformed in the following way.
|
||||
|
||||
| `IP` | `ipv6Subnet` | clientIP |
|
||||
|---------------------------|--------------|-----------------------|
|
||||
| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` |
|
||||
| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` |
|
||||
| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` |
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcecriterion.ipstrategy.ipv6Subnet=64"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-ipallowlist
|
||||
spec:
|
||||
ipallowlist:
|
||||
sourceCriterion:
|
||||
ipStrategy:
|
||||
ipv6Subnet: 64
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcecriterion.ipstrategy.ipv6Subnet=64"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
middlewares:
|
||||
test-ipallowlist:
|
||||
ipallowlist:
|
||||
sourceCriterion:
|
||||
ipStrategy:
|
||||
ipv6Subnet: 64
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.middlewares]
|
||||
[http.middlewares.test-ipallowlist.ipallowlist]
|
||||
[http.middlewares.test-ipallowlist.ipallowlist.sourceCriterion.ipStrategy]
|
||||
ipv6Subnet = 64
|
||||
```
|
||||
|
|
|
@ -81,6 +81,9 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and take th
|
|||
- 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 less than or equal to 0.
|
||||
|
||||
If `ipStrategy.ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
|
||||
See [ipStrategy.ipv6Subnet](#ipstrategyipv6subnet) for more details.
|
||||
|
||||
!!! example "Examples of Depth & X-Forwarded-For"
|
||||
|
||||
If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP is `"10.0.0.1"` (at depth 4) but the IP used for the whitelisting is `"12.0.0.1"` (`depth=2`).
|
||||
|
@ -210,3 +213,60 @@ http:
|
|||
[http.middlewares.test-ipwhitelist.ipWhiteList.ipStrategy]
|
||||
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
```
|
||||
|
||||
#### `ipStrategy.ipv6Subnet`
|
||||
|
||||
This strategy applies to `Depth` and `RemoteAddr` strategy only.
|
||||
If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
|
||||
|
||||
This is useful for grouping IPv6 addresses into subnets to prevent bypassing this middleware by obtaining a new IPv6.
|
||||
|
||||
- `ipv6Subnet` is ignored if its value is outside of 0-128 interval
|
||||
|
||||
!!! example "Example of ipv6Subnet"
|
||||
|
||||
If `ipv6Subnet` is provided, the IP is transformed in the following way.
|
||||
|
||||
| `IP` | `ipv6Subnet` | clientIP |
|
||||
|---------------------------|--------------|-----------------------|
|
||||
| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` |
|
||||
| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` |
|
||||
| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` |
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ipWhiteList.ipWhiteList.sourcecriterion.ipstrategy.ipv6Subnet=64"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-ipWhiteList
|
||||
spec:
|
||||
ipWhiteList:
|
||||
sourceCriterion:
|
||||
ipStrategy:
|
||||
ipv6Subnet: 64
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.middlewares.test-ipWhiteList.ipWhiteList.sourcecriterion.ipstrategy.ipv6Subnet=64"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
middlewares:
|
||||
test-ipWhiteList:
|
||||
ipWhiteList:
|
||||
sourceCriterion:
|
||||
ipStrategy:
|
||||
ipv6Subnet: 64
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.middlewares]
|
||||
[http.middlewares.test-ipWhiteList.ipWhiteList]
|
||||
[http.middlewares.test-ipWhiteList.ipWhiteList.sourceCriterion.ipStrategy]
|
||||
ipv6Subnet = 64
|
||||
```
|
||||
|
|
|
@ -211,7 +211,7 @@ If none are set, the default is to use the request's remote address field (as an
|
|||
|
||||
#### `sourceCriterion.ipStrategy`
|
||||
|
||||
The `ipStrategy` option defines two parameters that configures how Traefik determines the client IP: `depth`, and `excludedIPs`.
|
||||
The `ipStrategy` option defines three parameters that configures how Traefik determines the client IP: `depth`, `excludedIPs` and `ipv6Subnet`.
|
||||
|
||||
!!! important "As a middleware, rate-limiting happens before the actual proxying to the backend takes place. In addition, the previous network hop only gets appended to `X-Forwarded-For` during the last stages of proxying, i.e. after it has already passed through rate-limiting. Therefore, during rate-limiting, as the previous network hop is not yet present in `X-Forwarded-For`, it cannot be found and/or relied upon."
|
||||
|
||||
|
@ -222,6 +222,9 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and select
|
|||
- If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP is empty.
|
||||
- `depth` is ignored if its value is less than or equal to 0.
|
||||
|
||||
If `ipStrategy.ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
|
||||
See [ipStrategy.ipv6Subnet](#ipstrategyipv6subnet) for more details.
|
||||
|
||||
!!! example "Example of Depth & X-Forwarded-For"
|
||||
|
||||
If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP is `"10.0.0.1"` (at depth 4) but the IP used as the criterion is `"12.0.0.1"` (`depth=2`).
|
||||
|
@ -355,6 +358,63 @@ http:
|
|||
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
```
|
||||
|
||||
##### `ipStrategy.ipv6Subnet`
|
||||
|
||||
This strategy applies to `Depth` and `RemoteAddr` strategy only.
|
||||
If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
|
||||
|
||||
This is useful for grouping IPv6 addresses into subnets to prevent bypassing this middleware by obtaining a new IPv6.
|
||||
|
||||
- `ipv6Subnet` is ignored if its value is outside of 0-128 interval
|
||||
|
||||
!!! example "Example of ipv6Subnet"
|
||||
|
||||
If `ipv6Subnet` is provided, the IP is transformed in the following way.
|
||||
|
||||
| `IP` | `ipv6Subnet` | clientIP |
|
||||
|---------------------------|--------------|-----------------------|
|
||||
| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` |
|
||||
| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` |
|
||||
| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` |
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.ipv6Subnet=64"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-ratelimit
|
||||
spec:
|
||||
ratelimit:
|
||||
sourceCriterion:
|
||||
ipStrategy:
|
||||
ipv6Subnet: 64
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.ipv6Subnet=64"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
middlewares:
|
||||
test-ratelimit:
|
||||
ratelimit:
|
||||
sourceCriterion:
|
||||
ipStrategy:
|
||||
ipv6Subnet: 64
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.middlewares]
|
||||
[http.middlewares.test-ratelimit.ratelimit]
|
||||
[http.middlewares.test-ratelimit.ratelimit.sourceCriterion.ipStrategy]
|
||||
ipv6Subnet = 64
|
||||
```
|
||||
|
||||
#### `sourceCriterion.requestHeaderName`
|
||||
|
||||
Name of the header used to group incoming requests.
|
||||
|
|
|
@ -85,15 +85,18 @@
|
|||
- "traefik.http.middlewares.middleware13.ipallowlist.ipstrategy=true"
|
||||
- "traefik.http.middlewares.middleware13.ipallowlist.ipstrategy.depth=42"
|
||||
- "traefik.http.middlewares.middleware13.ipallowlist.ipstrategy.excludedips=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware13.ipallowlist.ipstrategy.ipv6subnet=42"
|
||||
- "traefik.http.middlewares.middleware13.ipallowlist.rejectstatuscode=42"
|
||||
- "traefik.http.middlewares.middleware13.ipallowlist.sourcerange=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware14.ipwhitelist.ipstrategy=true"
|
||||
- "traefik.http.middlewares.middleware14.ipwhitelist.ipstrategy.depth=42"
|
||||
- "traefik.http.middlewares.middleware14.ipwhitelist.ipstrategy.excludedips=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware14.ipwhitelist.ipstrategy.ipv6subnet=42"
|
||||
- "traefik.http.middlewares.middleware14.ipwhitelist.sourcerange=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware15.inflightreq.amount=42"
|
||||
- "traefik.http.middlewares.middleware15.inflightreq.sourcecriterion.ipstrategy.depth=42"
|
||||
- "traefik.http.middlewares.middleware15.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware15.inflightreq.sourcecriterion.ipstrategy.ipv6subnet=42"
|
||||
- "traefik.http.middlewares.middleware15.inflightreq.sourcecriterion.requestheadername=foobar"
|
||||
- "traefik.http.middlewares.middleware15.inflightreq.sourcecriterion.requesthost=true"
|
||||
- "traefik.http.middlewares.middleware16.passtlsclientcert.info.issuer.commonname=true"
|
||||
|
@ -125,6 +128,7 @@
|
|||
- "traefik.http.middlewares.middleware18.ratelimit.period=42s"
|
||||
- "traefik.http.middlewares.middleware18.ratelimit.sourcecriterion.ipstrategy.depth=42"
|
||||
- "traefik.http.middlewares.middleware18.ratelimit.sourcecriterion.ipstrategy.excludedips=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware18.ratelimit.sourcecriterion.ipstrategy.ipv6subnet=42"
|
||||
- "traefik.http.middlewares.middleware18.ratelimit.sourcecriterion.requestheadername=foobar"
|
||||
- "traefik.http.middlewares.middleware18.ratelimit.sourcecriterion.requesthost=true"
|
||||
- "traefik.http.middlewares.middleware19.redirectregex.permanent=true"
|
||||
|
|
|
@ -227,12 +227,14 @@
|
|||
[http.middlewares.Middleware13.ipAllowList.ipStrategy]
|
||||
depth = 42
|
||||
excludedIPs = ["foobar", "foobar"]
|
||||
ipv6Subnet = 42
|
||||
[http.middlewares.Middleware14]
|
||||
[http.middlewares.Middleware14.ipWhiteList]
|
||||
sourceRange = ["foobar", "foobar"]
|
||||
[http.middlewares.Middleware14.ipWhiteList.ipStrategy]
|
||||
depth = 42
|
||||
excludedIPs = ["foobar", "foobar"]
|
||||
ipv6Subnet = 42
|
||||
[http.middlewares.Middleware15]
|
||||
[http.middlewares.Middleware15.inFlightReq]
|
||||
amount = 42
|
||||
|
@ -242,6 +244,7 @@
|
|||
[http.middlewares.Middleware15.inFlightReq.sourceCriterion.ipStrategy]
|
||||
depth = 42
|
||||
excludedIPs = ["foobar", "foobar"]
|
||||
ipv6Subnet = 42
|
||||
[http.middlewares.Middleware16]
|
||||
[http.middlewares.Middleware16.passTLSClientCert]
|
||||
pem = true
|
||||
|
@ -286,6 +289,7 @@
|
|||
[http.middlewares.Middleware18.rateLimit.sourceCriterion.ipStrategy]
|
||||
depth = 42
|
||||
excludedIPs = ["foobar", "foobar"]
|
||||
ipv6Subnet = 42
|
||||
[http.middlewares.Middleware19]
|
||||
[http.middlewares.Middleware19.redirectRegex]
|
||||
regex = "foobar"
|
||||
|
|
|
@ -267,6 +267,7 @@ http:
|
|||
excludedIPs:
|
||||
- foobar
|
||||
- foobar
|
||||
ipv6Subnet: 42
|
||||
rejectStatusCode: 42
|
||||
Middleware14:
|
||||
ipWhiteList:
|
||||
|
@ -278,6 +279,7 @@ http:
|
|||
excludedIPs:
|
||||
- foobar
|
||||
- foobar
|
||||
ipv6Subnet: 42
|
||||
Middleware15:
|
||||
inFlightReq:
|
||||
amount: 42
|
||||
|
@ -287,6 +289,7 @@ http:
|
|||
excludedIPs:
|
||||
- foobar
|
||||
- foobar
|
||||
ipv6Subnet: 42
|
||||
requestHeaderName: foobar
|
||||
requestHost: true
|
||||
Middleware16:
|
||||
|
@ -333,6 +336,7 @@ http:
|
|||
excludedIPs:
|
||||
- foobar
|
||||
- foobar
|
||||
ipv6Subnet: 42
|
||||
requestHeaderName: foobar
|
||||
requestHost: true
|
||||
Middleware19:
|
||||
|
|
|
@ -1458,6 +1458,12 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider
|
||||
all IPv6 addresses from the defined subnet as originating
|
||||
from the same IP. Applies to RemoteAddrStrategy and
|
||||
DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
requestHeaderName:
|
||||
description: RequestHeaderName defines the name of the header
|
||||
|
@ -1491,6 +1497,11 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider all
|
||||
IPv6 addresses from the defined subnet as originating from
|
||||
the same IP. Applies to RemoteAddrStrategy and DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
rejectStatusCode:
|
||||
description: |-
|
||||
|
@ -1523,6 +1534,11 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider all
|
||||
IPv6 addresses from the defined subnet as originating from
|
||||
the same IP. Applies to RemoteAddrStrategy and DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
sourceRange:
|
||||
description: SourceRange defines the set of allowed IPs (or ranges
|
||||
|
@ -1691,6 +1707,12 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider
|
||||
all IPv6 addresses from the defined subnet as originating
|
||||
from the same IP. Applies to RemoteAddrStrategy and
|
||||
DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
requestHeaderName:
|
||||
description: RequestHeaderName defines the name of the header
|
||||
|
|
|
@ -103,18 +103,21 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/depth` | `42` |
|
||||
| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/excludedIPs/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/ipv6Subnet` | `42` |
|
||||
| `traefik/http/middlewares/Middleware13/ipAllowList/rejectStatusCode` | `42` |
|
||||
| `traefik/http/middlewares/Middleware13/ipAllowList/sourceRange/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware13/ipAllowList/sourceRange/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/depth` | `42` |
|
||||
| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/excludedIPs/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/excludedIPs/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/ipv6Subnet` | `42` |
|
||||
| `traefik/http/middlewares/Middleware14/ipWhiteList/sourceRange/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware14/ipWhiteList/sourceRange/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware15/inFlightReq/amount` | `42` |
|
||||
| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` |
|
||||
| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/ipv6Subnet` | `42` |
|
||||
| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/requestHost` | `true` |
|
||||
| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/commonName` | `true` |
|
||||
|
@ -147,6 +150,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/depth` | `42` |
|
||||
| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/ipv6Subnet` | `42` |
|
||||
| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/requestHeaderName` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/requestHost` | `true` |
|
||||
| `traefik/http/middlewares/Middleware19/redirectRegex/permanent` | `true` |
|
||||
|
|
|
@ -734,6 +734,12 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider
|
||||
all IPv6 addresses from the defined subnet as originating
|
||||
from the same IP. Applies to RemoteAddrStrategy and
|
||||
DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
requestHeaderName:
|
||||
description: RequestHeaderName defines the name of the header
|
||||
|
@ -767,6 +773,11 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider all
|
||||
IPv6 addresses from the defined subnet as originating from
|
||||
the same IP. Applies to RemoteAddrStrategy and DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
rejectStatusCode:
|
||||
description: |-
|
||||
|
@ -799,6 +810,11 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider all
|
||||
IPv6 addresses from the defined subnet as originating from
|
||||
the same IP. Applies to RemoteAddrStrategy and DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
sourceRange:
|
||||
description: SourceRange defines the set of allowed IPs (or ranges
|
||||
|
@ -967,6 +983,12 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider
|
||||
all IPv6 addresses from the defined subnet as originating
|
||||
from the same IP. Applies to RemoteAddrStrategy and
|
||||
DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
requestHeaderName:
|
||||
description: RequestHeaderName defines the name of the header
|
||||
|
|
|
@ -1458,6 +1458,12 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider
|
||||
all IPv6 addresses from the defined subnet as originating
|
||||
from the same IP. Applies to RemoteAddrStrategy and
|
||||
DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
requestHeaderName:
|
||||
description: RequestHeaderName defines the name of the header
|
||||
|
@ -1491,6 +1497,11 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider all
|
||||
IPv6 addresses from the defined subnet as originating from
|
||||
the same IP. Applies to RemoteAddrStrategy and DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
rejectStatusCode:
|
||||
description: |-
|
||||
|
@ -1523,6 +1534,11 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider all
|
||||
IPv6 addresses from the defined subnet as originating from
|
||||
the same IP. Applies to RemoteAddrStrategy and DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
sourceRange:
|
||||
description: SourceRange defines the set of allowed IPs (or ranges
|
||||
|
@ -1691,6 +1707,12 @@ spec:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipv6Subnet:
|
||||
description: IPv6Subnet configures Traefik to consider
|
||||
all IPv6 addresses from the defined subnet as originating
|
||||
from the same IP. Applies to RemoteAddrStrategy and
|
||||
DepthStrategy.
|
||||
type: integer
|
||||
type: object
|
||||
requestHeaderName:
|
||||
description: RequestHeaderName defines the name of the header
|
||||
|
|
57
pkg/config/dynamic/middleware_test.go
Normal file
57
pkg/config/dynamic/middleware_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package dynamic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_GetStrategy_ipv6Subnet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
expectError bool
|
||||
ipv6Subnet *int
|
||||
}{
|
||||
{
|
||||
desc: "Nil subnet",
|
||||
},
|
||||
{
|
||||
desc: "Zero subnet",
|
||||
expectError: true,
|
||||
ipv6Subnet: intPtr(0),
|
||||
},
|
||||
{
|
||||
desc: "Subnet greater that 128",
|
||||
expectError: true,
|
||||
ipv6Subnet: intPtr(129),
|
||||
},
|
||||
{
|
||||
desc: "Valid subnet",
|
||||
ipv6Subnet: intPtr(128),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
strategy := IPStrategy{
|
||||
IPv6Subnet: test.ipv6Subnet,
|
||||
}
|
||||
|
||||
get, err := strategy.Get()
|
||||
if test.expectError {
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, get)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, get)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func intPtr(value int) *int {
|
||||
return &value
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package dynamic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -405,6 +406,8 @@ type IPStrategy struct {
|
|||
Depth int `json:"depth,omitempty" toml:"depth,omitempty" yaml:"depth,omitempty" export:"true"`
|
||||
// ExcludedIPs configures Traefik to scan the X-Forwarded-For header and select the first IP not in the list.
|
||||
ExcludedIPs []string `json:"excludedIPs,omitempty" toml:"excludedIPs,omitempty" yaml:"excludedIPs,omitempty"`
|
||||
// IPv6Subnet configures Traefik to consider all IPv6 addresses from the defined subnet as originating from the same IP. Applies to RemoteAddrStrategy and DepthStrategy.
|
||||
IPv6Subnet *int `json:"ipv6Subnet,omitempty" toml:"ipv6Subnet,omitempty" yaml:"ipv6Subnet,omitempty"`
|
||||
// TODO(mpl): I think we should make RemoteAddr an explicit field. For one thing, it would yield better documentation.
|
||||
}
|
||||
|
||||
|
@ -418,8 +421,13 @@ func (s *IPStrategy) Get() (ip.Strategy, error) {
|
|||
}
|
||||
|
||||
if s.Depth > 0 {
|
||||
if s.IPv6Subnet != nil && (*s.IPv6Subnet <= 0 || *s.IPv6Subnet > 128) {
|
||||
return nil, fmt.Errorf("invalid IPv6 subnet %d value, should be greater to 0 and lower or equal to 128", *s.IPv6Subnet)
|
||||
}
|
||||
|
||||
return &ip.DepthStrategy{
|
||||
Depth: s.Depth,
|
||||
Depth: s.Depth,
|
||||
IPv6Subnet: s.IPv6Subnet,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -433,7 +441,13 @@ func (s *IPStrategy) Get() (ip.Strategy, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
return &ip.RemoteAddrStrategy{}, nil
|
||||
if s.IPv6Subnet != nil && (*s.IPv6Subnet <= 0 || *s.IPv6Subnet > 128) {
|
||||
return nil, fmt.Errorf("invalid IPv6 subnet %d value, should be greater to 0 and lower or equal to 128", *s.IPv6Subnet)
|
||||
}
|
||||
|
||||
return &ip.RemoteAddrStrategy{
|
||||
IPv6Subnet: s.IPv6Subnet,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
@ -704,6 +704,11 @@ func (in *IPStrategy) DeepCopyInto(out *IPStrategy) {
|
|||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.IPv6Subnet != nil {
|
||||
in, out := &in.IPv6Subnet, &out.IPv6Subnet
|
||||
*out = new(int)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -90,10 +90,12 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.http.middlewares.Middleware8.headers.stsseconds": "42",
|
||||
"traefik.http.middlewares.Middleware9.ipallowlist.ipstrategy.depth": "42",
|
||||
"traefik.http.middlewares.Middleware9.ipallowlist.ipstrategy.excludedips": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware9.ipallowlist.ipstrategy.ipv6subnet": "42",
|
||||
"traefik.http.middlewares.Middleware9.ipallowlist.sourcerange": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware10.inflightreq.amount": "42",
|
||||
"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.ipstrategy.depth": "42",
|
||||
"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.ipstrategy.excludedips": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.ipstrategy.ipv6subnet": "42",
|
||||
"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.requestheadername": "foobar",
|
||||
"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.requesthost": "true",
|
||||
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.notafter": "true",
|
||||
|
@ -123,6 +125,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.requesthost": "true",
|
||||
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.ipstrategy.depth": "42",
|
||||
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.ipstrategy.excludedips": "foobar, foobar",
|
||||
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.ipstrategy.ipv6subnet": "42",
|
||||
"traefik.http.middlewares.Middleware13.redirectregex.permanent": "true",
|
||||
"traefik.http.middlewares.Middleware13.redirectregex.regex": "foobar",
|
||||
"traefik.http.middlewares.Middleware13.redirectregex.replacement": "foobar",
|
||||
|
@ -392,6 +395,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
IPStrategy: &dynamic.IPStrategy{
|
||||
Depth: 42,
|
||||
ExcludedIPs: []string{"foobar", "fiibar"},
|
||||
IPv6Subnet: intPtr(42),
|
||||
},
|
||||
RequestHeaderName: "foobar",
|
||||
RequestHost: true,
|
||||
|
@ -437,6 +441,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
IPStrategy: &dynamic.IPStrategy{
|
||||
Depth: 42,
|
||||
ExcludedIPs: []string{"foobar", "foobar"},
|
||||
IPv6Subnet: intPtr(42),
|
||||
},
|
||||
RequestHeaderName: "foobar",
|
||||
RequestHost: true,
|
||||
|
@ -648,6 +653,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
IPv6Subnet: intPtr(42),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -913,6 +919,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
IPStrategy: &dynamic.IPStrategy{
|
||||
Depth: 42,
|
||||
ExcludedIPs: []string{"foobar", "fiibar"},
|
||||
IPv6Subnet: intPtr(42),
|
||||
},
|
||||
RequestHeaderName: "foobar",
|
||||
RequestHost: true,
|
||||
|
@ -957,6 +964,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
IPStrategy: &dynamic.IPStrategy{
|
||||
Depth: 42,
|
||||
ExcludedIPs: []string{"foobar", "foobar"},
|
||||
IPv6Subnet: intPtr(42),
|
||||
},
|
||||
RequestHeaderName: "foobar",
|
||||
RequestHost: true,
|
||||
|
@ -1176,6 +1184,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
IPv6Subnet: intPtr(42),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1338,11 +1347,13 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.Depth": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.ExcludedIPs": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.IPv6Subnet": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.RejectStatusCode": "0",
|
||||
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.SourceRange": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.Amount": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.Depth": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.ExcludedIPs": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.IPv6Subnet": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.RequestHeaderName": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.RequestHost": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotAfter": "true",
|
||||
|
@ -1372,6 +1383,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.RequestHost": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.IPStrategy.Depth": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.IPStrategy.ExcludedIPs": "foobar, foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.IPStrategy.IPv6Subnet": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Regex": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Replacement": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Permanent": "true",
|
||||
|
@ -1486,3 +1498,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, expected, labels)
|
||||
}
|
||||
|
||||
func intPtr(value int) *int {
|
||||
return &value
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package ip
|
|||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -16,7 +17,10 @@ type Strategy interface {
|
|||
}
|
||||
|
||||
// RemoteAddrStrategy a strategy that always return the remote address.
|
||||
type RemoteAddrStrategy struct{}
|
||||
type RemoteAddrStrategy struct {
|
||||
// IPv6Subnet instructs the strategy to return the first IP of the subnet where IP belongs.
|
||||
IPv6Subnet *int
|
||||
}
|
||||
|
||||
// GetIP returns the selected IP.
|
||||
func (s *RemoteAddrStrategy) GetIP(req *http.Request) string {
|
||||
|
@ -24,15 +28,22 @@ func (s *RemoteAddrStrategy) GetIP(req *http.Request) string {
|
|||
if err != nil {
|
||||
return req.RemoteAddr
|
||||
}
|
||||
|
||||
if s.IPv6Subnet != nil {
|
||||
return getIPv6SubnetIP(ip, *s.IPv6Subnet)
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
// DepthStrategy a strategy based on the depth inside the X-Forwarded-For from right to left.
|
||||
type DepthStrategy struct {
|
||||
Depth int
|
||||
// IPv6Subnet instructs the strategy to return the first IP of the subnet where IP belongs.
|
||||
IPv6Subnet *int
|
||||
}
|
||||
|
||||
// GetIP return the selected IP.
|
||||
// GetIP returns the selected IP.
|
||||
func (s *DepthStrategy) GetIP(req *http.Request) string {
|
||||
xff := req.Header.Get(xForwardedFor)
|
||||
xffs := strings.Split(xff, ",")
|
||||
|
@ -40,7 +51,14 @@ func (s *DepthStrategy) GetIP(req *http.Request) string {
|
|||
if len(xffs) < s.Depth {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(xffs[len(xffs)-s.Depth])
|
||||
|
||||
ip := strings.TrimSpace(xffs[len(xffs)-s.Depth])
|
||||
|
||||
if s.IPv6Subnet != nil {
|
||||
return getIPv6SubnetIP(ip, *s.IPv6Subnet)
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
// PoolStrategy is a strategy based on an IP Checker.
|
||||
|
@ -72,3 +90,23 @@ func (s *PoolStrategy) GetIP(req *http.Request) string {
|
|||
|
||||
return ""
|
||||
}
|
||||
|
||||
// getIPv6SubnetIP returns the IPv6 subnet IP.
|
||||
// It returns the original IP when it is not an IPv6, or if parsing the IP has failed with an error.
|
||||
func getIPv6SubnetIP(ip string, ipv6Subnet int) string {
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
if !addr.Is6() {
|
||||
return ip
|
||||
}
|
||||
|
||||
prefix, err := addr.Prefix(ipv6Subnet)
|
||||
if err != nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
return prefix.Addr().String()
|
||||
}
|
||||
|
|
|
@ -9,23 +9,81 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
ipv6Basic = "::abcd:ffff:c0a8:1"
|
||||
ipv6BracketsPort = "[::abcd:ffff:c0a8:1]:80"
|
||||
ipv6BracketsZonePort = "[::abcd:ffff:c0a8:1%1]:80"
|
||||
)
|
||||
|
||||
func TestRemoteAddrStrategy_GetIP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
expected string
|
||||
desc string
|
||||
expected string
|
||||
remoteAddr string
|
||||
ipv6Subnet *int
|
||||
}{
|
||||
// Valid IP format
|
||||
{
|
||||
desc: "Use RemoteAddr",
|
||||
desc: "Use RemoteAddr, ipv4",
|
||||
expected: "192.0.2.1",
|
||||
},
|
||||
{
|
||||
desc: "Use RemoteAddr, ipv6 brackets with port, no IPv6 subnet",
|
||||
remoteAddr: ipv6BracketsPort,
|
||||
expected: "::abcd:ffff:c0a8:1",
|
||||
},
|
||||
{
|
||||
desc: "Use RemoteAddr, ipv6 brackets with zone and port, no IPv6 subnet",
|
||||
remoteAddr: ipv6BracketsZonePort,
|
||||
expected: "::abcd:ffff:c0a8:1%1",
|
||||
},
|
||||
|
||||
// Invalid IPv6 format
|
||||
{
|
||||
desc: "Use RemoteAddr, ipv6 basic, missing brackets, no IPv6 subnet",
|
||||
remoteAddr: ipv6Basic,
|
||||
expected: ipv6Basic,
|
||||
},
|
||||
|
||||
// Valid IP format with subnet
|
||||
{
|
||||
desc: "Use RemoteAddr, ipv4, ignore subnet",
|
||||
expected: "192.0.2.1",
|
||||
ipv6Subnet: intPtr(24),
|
||||
},
|
||||
{
|
||||
desc: "Use RemoteAddr, ipv6 brackets with port, subnet",
|
||||
remoteAddr: ipv6BracketsPort,
|
||||
expected: "::abcd:0:0:0",
|
||||
ipv6Subnet: intPtr(80),
|
||||
},
|
||||
{
|
||||
desc: "Use RemoteAddr, ipv6 brackets with zone and port, subnet",
|
||||
remoteAddr: ipv6BracketsZonePort,
|
||||
expected: "::abcd:0:0:0",
|
||||
ipv6Subnet: intPtr(80),
|
||||
},
|
||||
|
||||
// Valid IP, invalid subnet
|
||||
{
|
||||
desc: "Use RemoteAddr, ipv6 brackets with port, invalid subnet",
|
||||
remoteAddr: ipv6BracketsPort,
|
||||
expected: "::abcd:ffff:c0a8:1",
|
||||
ipv6Subnet: intPtr(500),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
strategy := RemoteAddrStrategy{}
|
||||
strategy := RemoteAddrStrategy{
|
||||
IPv6Subnet: test.ipv6Subnet,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
|
||||
if test.remoteAddr != "" {
|
||||
req.RemoteAddr = test.remoteAddr
|
||||
}
|
||||
actual := strategy.GetIP(req)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
|
@ -38,6 +96,7 @@ func TestDepthStrategy_GetIP(t *testing.T) {
|
|||
depth int
|
||||
xForwardedFor string
|
||||
expected string
|
||||
ipv6Subnet *int
|
||||
}{
|
||||
{
|
||||
desc: "Use depth",
|
||||
|
@ -57,13 +116,30 @@ func TestDepthStrategy_GetIP(t *testing.T) {
|
|||
xForwardedFor: "10.0.0.2,10.0.0.1",
|
||||
expected: "10.0.0.2",
|
||||
},
|
||||
{
|
||||
desc: "Use depth with IPv4 subnet",
|
||||
depth: 2,
|
||||
xForwardedFor: "10.0.0.3,10.0.0.2,10.0.0.1",
|
||||
expected: "10.0.0.2",
|
||||
ipv6Subnet: intPtr(80),
|
||||
},
|
||||
{
|
||||
desc: "Use depth with IPv6 subnet",
|
||||
depth: 2,
|
||||
xForwardedFor: "10.0.0.3," + ipv6Basic + ",10.0.0.1",
|
||||
expected: "::abcd:0:0:0",
|
||||
ipv6Subnet: intPtr(80),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
strategy := DepthStrategy{Depth: test.depth}
|
||||
strategy := DepthStrategy{
|
||||
Depth: test.depth,
|
||||
IPv6Subnet: test.ipv6Subnet,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
|
||||
req.Header.Set(xForwardedFor, test.xForwardedFor)
|
||||
actual := strategy.GetIP(req)
|
||||
|
@ -121,3 +197,7 @@ func TestTrustedIPsStrategy_GetIP(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func intPtr(value int) *int {
|
||||
return &value
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue