doc: clarify usage for ratelimit's excludedIPs
This commit is contained in:
parent
c10c7619d3
commit
2560626419
4 changed files with 62 additions and 28 deletions
|
@ -325,19 +325,46 @@ http:
|
||||||
|
|
||||||
##### `ipStrategy.excludedIPs`
|
##### `ipStrategy.excludedIPs`
|
||||||
|
|
||||||
`excludedIPs` configures Traefik to scan the `X-Forwarded-For` header and select the first IP not in the list.
|
!!! important "Contrary to what the name might suggest, this option is _not_ about excluding an IP from the rate limiter, and therefore cannot be used to deactivate rate limiting for some IPs."
|
||||||
|
|
||||||
!!! important "If `depth` is specified, `excludedIPs` is ignored."
|
!!! important "If `depth` is specified, `excludedIPs` is ignored."
|
||||||
|
|
||||||
!!! example "Example of ExcludedIPs & X-Forwarded-For"
|
`excludedIPs` is meant to address two classes of somewhat distinct use-cases:
|
||||||
|
|
||||||
| `X-Forwarded-For` | `excludedIPs` | clientIP |
|
1. Distinguish IPs which are behind the same (set of) reverse-proxies so that each of them contributes, independently to the others,
|
||||||
|-----------------------------------------|-----------------------|--------------|
|
to its own rate-limit "bucket" (cf the [leaky bucket analogy](https://wikipedia.org/wiki/Leaky_bucket)).
|
||||||
| `"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"` |
|
In this case, `excludedIPs` should be set to match the list of `X-Forwarded-For IPs` that are to be excluded,
|
||||||
| `"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"` |
|
in order to find the actual clientIP.
|
||||||
| `"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"` |
|
!!! example "Each IP as a distinct source"
|
||||||
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
|
|
||||||
|
| X-Forwarded-For | excludedIPs | clientIP |
|
||||||
|
|--------------------------------|-----------------------|--------------|
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1"` | `"11.0.0.1,12.0.0.1"` | `"10.0.0.1"` |
|
||||||
|
| `"10.0.0.2,11.0.0.1,12.0.0.1"` | `"11.0.0.1,12.0.0.1"` | `"10.0.0.2"` |
|
||||||
|
|
||||||
|
2. Group together a set of IPs (also behind a common set of reverse-proxies) so that they are considered the same source,
|
||||||
|
and all contribute to the same rate-limit bucket.
|
||||||
|
|
||||||
|
!!! example "Group IPs together as same source"
|
||||||
|
|
||||||
|
| X-Forwarded-For | excludedIPs | clientIP |
|
||||||
|
|--------------------------------|--------------|--------------|
|
||||||
|
| `"10.0.0.1,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` |
|
||||||
|
| `"10.0.0.2,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` |
|
||||||
|
| `"10.0.0.3,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` |
|
||||||
|
|
||||||
|
For completeness, below are additional examples to illustrate how the matching works.
|
||||||
|
For a given request the list of `X-Forwarded-For` IPs is checked from most recent to most distant against the `excludedIPs` pool,
|
||||||
|
and the first IP that is _not_ in the pool (if any) is returned.
|
||||||
|
|
||||||
|
!!! example "Matching for clientIP"
|
||||||
|
|
||||||
|
| X-Forwarded-For | excludedIPs | clientIP |
|
||||||
|
|--------------------------------|-----------------------|--------------|
|
||||||
|
| `"10.0.0.1,11.0.0.1,13.0.0.1"` | `"11.0.0.1"` | `"13.0.0.1"` |
|
||||||
|
| `"10.0.0.1,11.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"
|
```yaml tab="Docker"
|
||||||
labels:
|
labels:
|
||||||
|
|
|
@ -241,7 +241,7 @@ type IPStrategy struct {
|
||||||
|
|
||||||
// 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 based 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) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
|
@ -259,7 +259,7 @@ func (s *IPStrategy) Get() (ip.Strategy, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ip.CheckerStrategy{
|
return &ip.PoolStrategy{
|
||||||
Checker: checker,
|
Checker: checker,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,14 +43,16 @@ func (s *DepthStrategy) GetIP(req *http.Request) string {
|
||||||
return strings.TrimSpace(xffs[len(xffs)-s.Depth])
|
return strings.TrimSpace(xffs[len(xffs)-s.Depth])
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckerStrategy a strategy based on an IP Checker
|
// PoolStrategy is a strategy based on an IP Checker.
|
||||||
// allows to check that addresses are in a trusted IPs.
|
// It allows to check whether addresses are in a given pool of IPs.
|
||||||
type CheckerStrategy struct {
|
type PoolStrategy struct {
|
||||||
Checker *Checker
|
Checker *Checker
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIP return the selected IP.
|
// GetIP checks the list of Forwarded IPs (most recent first) against the
|
||||||
func (s *CheckerStrategy) GetIP(req *http.Request) string {
|
// Checker pool of IPs. It returns the first IP that is not in the pool, or the
|
||||||
|
// empty string otherwise.
|
||||||
|
func (s *PoolStrategy) GetIP(req *http.Request) string {
|
||||||
if s.Checker == nil {
|
if s.Checker == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -60,9 +62,13 @@ func (s *CheckerStrategy) GetIP(req *http.Request) string {
|
||||||
|
|
||||||
for i := len(xffs) - 1; i >= 0; i-- {
|
for i := len(xffs) - 1; i >= 0; i-- {
|
||||||
xffTrimmed := strings.TrimSpace(xffs[i])
|
xffTrimmed := strings.TrimSpace(xffs[i])
|
||||||
|
if len(xffTrimmed) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if contain, _ := s.Checker.Contains(xffTrimmed); !contain {
|
if contain, _ := s.Checker.Contains(xffTrimmed); !contain {
|
||||||
return xffTrimmed
|
return xffTrimmed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,34 +74,35 @@ func TestDepthStrategy_GetIP(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcludedIPsStrategy_GetIP(t *testing.T) {
|
func TestTrustedIPsStrategy_GetIP(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
excludedIPs []string
|
trustedIPs []string
|
||||||
xForwardedFor string
|
xForwardedFor string
|
||||||
expected string
|
expected string
|
||||||
|
useRemote bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "Use excluded all IPs",
|
desc: "Trust all IPs",
|
||||||
excludedIPs: []string{"10.0.0.4", "10.0.0.3", "10.0.0.2", "10.0.0.1"},
|
trustedIPs: []string{"10.0.0.4", "10.0.0.3", "10.0.0.2", "10.0.0.1"},
|
||||||
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
||||||
expected: "",
|
expected: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Use excluded IPs",
|
desc: "Do not trust all IPs",
|
||||||
excludedIPs: []string{"10.0.0.2", "10.0.0.1"},
|
trustedIPs: []string{"10.0.0.2", "10.0.0.1"},
|
||||||
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
||||||
expected: "10.0.0.3",
|
expected: "10.0.0.3",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Use excluded IPs CIDR",
|
desc: "Do not trust all IPs with CIDR",
|
||||||
excludedIPs: []string{"10.0.0.1/24"},
|
trustedIPs: []string{"10.0.0.1/24"},
|
||||||
xForwardedFor: "127.0.0.1,10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
xForwardedFor: "127.0.0.1,10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
||||||
expected: "127.0.0.1",
|
expected: "127.0.0.1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Use excluded all IPs CIDR",
|
desc: "Trust all IPs with CIDR",
|
||||||
excludedIPs: []string{"10.0.0.1/24"},
|
trustedIPs: []string{"10.0.0.1/24"},
|
||||||
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
xForwardedFor: "10.0.0.4,10.0.0.3,10.0.0.2,10.0.0.1",
|
||||||
expected: "",
|
expected: "",
|
||||||
},
|
},
|
||||||
|
@ -112,10 +113,10 @@ func TestExcludedIPsStrategy_GetIP(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
checker, err := NewChecker(test.excludedIPs)
|
checker, err := NewChecker(test.trustedIPs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
strategy := CheckerStrategy{Checker: checker}
|
strategy := PoolStrategy{Checker: checker}
|
||||||
req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
|
req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
|
||||||
req.Header.Set(xForwardedFor, test.xForwardedFor)
|
req.Header.Set(xForwardedFor, test.xForwardedFor)
|
||||||
actual := strategy.GetIP(req)
|
actual := strategy.GetIP(req)
|
||||||
|
|
Loading…
Reference in a new issue