Repository: traefik/traefik (language: go)

Issue:
Traefik uses a different set of XFF header values for its whitelisting middleware than what it passes on
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you do?

First off, I'm the person who created this post on the community forum: https://community.traefik.io/t/setting-up-both-forwardedheaders-and-ipwhitelist-or-how-to-trust-ip-and-whitelist-the-proxy-not-the-end-user/17423 (just to let you know that I did indeed search the forum).

So requests are being made like this:

    me ---> Cloudflare ---> Traefik

Basically, when trusting forwarded headers (with the `forwardedHeaders.trustedIPs` config in the `entryPoint`, Traefik adds on the "last requester"'s IP just like many other reverse proxies do.

So if we were to track the XFF values across the request, it would look something like this:

    N/A ---> $my_ip ---> $my_ip, $cloudflare_ip

However, I noticed that the XFF that is being _passed on_ to the applications behind it (i.e. `$my_ip, $cloudflare_ip` in this case) is NOT the XFF that Traefik works off of when doing IP Whitelisting!

In other words, when Traefik looks up the IP using `ipStrategy.depth = 1`, I'd *expect* to Traefik to look at the rightmost value in the XFF header `$my_ip, $cloudflare_ip` (i.e. the XFF value it passes on to `whoami`).

### What did you see instead?

Instead, Traefik looks at the rightmost value in the XFF header `$my_ip` (i.e. the value that it receives from Cloudflare, NOT the value it passes onto the services sitting behind it).

I've scoured through the docs, and the difference between "XFF value we _received_" vs. "XFF value we're _passing on_" is _never_ documented anywhere, especially not in the ip whitelisting middleware section (where it talks about reading values from the XFF header).

So although it may be "working as intended", I'm filing this under the "bug" category because it is certainly not what is _expected_, at least based on the documentation. Thus, I'd like to see the documentation clarify the fact that it's working off of the _received_ XFF, not the XFF that Traefik _adds on_ to.

Secondly, I would greatly appreciate it if Traefik were to add an option somewhere to allow, well, basically anything reading off of the XFF header to use _the value that's being passed on_, rather than the value that's being _received_.

My use case is that I'd like to block all traffic hitting Traefik that is NOT being proxied by Cloudflare... but as of now, it is not possible to do this in Traefik due to the fact that the XFF header it works off of (remember, there are two distinct values here) _doesn't_ include Cloudflare's IP (even though the XFF being passed on _does_).

So if an option was added to change "which" XFF value Traefik looks at for things like the IP Whitelist middleware - the one it receives, vs. the one it passes on - I would be able to get the IP Whitelist middleware to work off of the latter, not the former, and IP Whitelist based on Cloudflare's IP, not the "real" IP.

Thank you.

### What version of Traefik are you using?

2.9.6

### What is your environment & configuration?

Please see the community post above for more details, but here is the configuration for the whitelist middleware:

```yml
cloudflare-whitelist:
  ipWhiteList:
    sourceRange: $list_of_cloudflare_ips
    ipStrategy:
      depth: 1
```

And I also have a `forwardedHeaders.trustedIPs` setup so that Traefik "trusts" and passes on the XFF values (this is confirmed working).

### If applicable, please paste the log output in DEBUG level

_No response_


Discussion / hints:

> ### What did you see instead?
> Instead, Traefik looks at the rightmost value in the XFF header `$my_ip` (i.e. the value that it receives from Cloudflare, NOT the value it passes onto the services sitting behind it).

Yes, it is indeed working as intended/expected. If you think about it, whitelisting being a middleware, it happens before the request is passed on to the core of the reverse-proxy (the stdlib reverse-proxy), which is the one in charge of appending the last hop (Cloudflare in your case) to the XFF header.
So it is expected that whitelisting (based on XFF) is not yet aware of the last hop, and cannot do what you're trying to achieve that way.

> I've scoured through the docs, and the difference between "XFF value we _received_" vs. "XFF value we're _passing on_" is _never_ documented anywhere, especially not in the ip whitelisting middleware section (where it talks about reading values from the XFF header).
> 
> So although it may be "working as intended", I'm filing this under the "bug" category because it is certainly not what is _expected_, at least based on the documentation. Thus, I'd like to see the documentation clarify the fact that it's working off of the _received_ XFF, not the XFF that Traefik _adds on_ to.

Alright. I haven't double-checked yet, but I'll mark this issue as a documentation bug for now, and we'll see what we can do about it later.

> Secondly, I would greatly appreciate it if Traefik were to add an option somewhere to allow, well, basically anything reading off of the XFF header to use _the value that's being passed on_, rather than the value that's being _received_.

Because of what I explained above, I don't think we would be likely to do that, however... (read below)

> My use case is that I'd like to block all traffic hitting Traefik that is NOT being proxied by Cloudflare... but as of now, it is not possible to do this in Traefik due to the fact that the XFF header it works off of (remember, there are two distinct values here) _doesn't_ include Cloudflare's IP (even though the XFF being passed on _does_).

AFAIR, if you do not set up any ipstrategy for your whitelisting, the RemoteAddrStrategy should be used by default. It is based on the last hop (the remote address), rather than on the XFF, so we believe it should work for your use case.
Could you please try it out and let us know?
This "detail" might very well be missing from the documentation too. In which case, we'll fix it as well when addressing this issue.



> Alright. I haven't double-checked yet, but I'll mark this issue as a documentation bug for now, and we'll see what we can do about it later.

Thank you.

> AFAIR, if you do not set up any ipstrategy for your whitelisting, the RemoteAddrStrategy should be used by default. It is based on the last hop (the remote address), rather than on the XFF, so we believe it should work for your use case.

...yes, it is missing from the documentation (if only I knew it was an option... my 5 hours :/). Ugh. I'll try it and let you know how it goes.
Just confirmed - it does work when I _don't_ set ipStrategy... the reason for the confusion (other than the fact that this behaviour is not documented) is that I naturally assumed that ipStrategy was a way to make the IP detection _more accurate_, i.e.: the method will stay the same (for both ipStrategy and depth, they both look at XFF), but the _way in which it's doing so_ it's different and _only_ the way in which it looks up the XFF is different...

Never have I thought that Traefik would just use a _completely_ different method of identifying the IP.

The documentation definitely needs a clarification on this as well.

Fix patch:
diff --git a/docs/content/middlewares/http/inflightreq.md b/docs/content/middlewares/http/inflightreq.md
index b3712b148f..f6993785a3 100644
--- a/docs/content/middlewares/http/inflightreq.md
+++ b/docs/content/middlewares/http/inflightreq.md
@@ -127,6 +127,8 @@ If none are set, the default is to use the `requestHost`.
 
 The `ipStrategy` option defines two parameters that configures how Traefik determines the client IP: `depth`, and `excludedIPs`.
 
+!!! 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."
+
 ##### `ipStrategy.depth`
 
 The `depth` option tells Traefik to use the `X-Forwarded-For` header and select the IP located at the `depth` position (starting from the right).
diff --git a/docs/content/middlewares/http/ipwhitelist.md b/docs/content/middlewares/http/ipwhitelist.md
index 3817b54d51..05dbf4d6f2 100644
--- a/docs/content/middlewares/http/ipwhitelist.md
+++ b/docs/content/middlewares/http/ipwhitelist.md
@@ -75,7 +75,10 @@ The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using
 
 ### `ipStrategy`
 
-The `ipStrategy` option defines two parameters that set how Traefik determines the client IP: `depth`, and `excludedIPs`.
+The `ipStrategy` option defines two parameters that set how Traefik determines the client IP: `depth`, and `excludedIPs`.  
+If no strategy is set, the default behavior is to match `sourceRange` against the Remote address found in the request.
+
+!!! important "As a middleware, whitelisting 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 whitelisting. Therefore, during whitelisting, as the previous network hop is not yet present in `X-Forwarded-For`, it cannot be matched against `sourceRange`."
 
 #### `ipStrategy.depth`
 
diff --git a/docs/content/middlewares/http/ratelimit.md b/docs/content/middlewares/http/ratelimit.md
index 8d62afd9c6..923f01d0c2 100644
--- a/docs/content/middlewares/http/ratelimit.md
+++ b/docs/content/middlewares/http/ratelimit.md
@@ -262,6 +262,8 @@ If none are set, the default is to use the request's remote address field (as an
 
 The `ipStrategy` option defines two parameters that configures how Traefik determines the client IP: `depth`, and `excludedIPs`.
 
+!!! 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."
+
 ##### `ipStrategy.depth`
 
 The `depth` option tells Traefik to use the `X-Forwarded-For` header and select the IP located at the `depth` position (starting from the right).


Test patch: