X-Forwarded-For improvements and bug fixes#15204
X-Forwarded-For improvements and bug fixes#15204balloob merged 4 commits intohome-assistant:devfrom colinodell:trusted-proxies-setting
Conversation
Per Wikipedia (https://en.wikipedia.org/wiki/X-Forwarded-For#Format): > The last IP address is always the IP address that connects to the last proxy, > which means it is the most reliable source of information.
|
I have tested this functionality locally and on a VPS with a proxy, and everything seems to work correctly. Like before, if anyone else would like to test this to confirm my results I'd appreciate it. Otherwise, this should be ready to go once the Travis tests pass. |
|
@colinodell thanks for the work. This is very good. The only issue I can see is backward compatibility. If trusted_proxies is not defined, where doe sit fall back to? [] or trusted_network? |
|
@colinodell also, what happens if a middleware module throws an exception? For example of somebody passes nonsense to the header and the |
| assert text != '255.255.255.255' | ||
|
|
||
|
|
||
| async def test_use_x_forwarded_for_with_trusted_proxy_and_possible_spoofed_header(aiohttp_client): |
There was a problem hiding this comment.
line too long (98 > 79 characters)
|
@ayavilevich I really appreciate your continued feedback on this! I have added a couple new tests in commit 6bf513d to cover some of the scenarios you outlined in the other thread. (Because
It falls back to We don't want to fall back to
Good call, I have added a Let me know if this looks good or if there's anything else you'd like to see here. |
|
@colinodell looks good, here are some comments which you need to check potentially. You are right, What does I think that you need to have Is any use case with more than one trusted proxy in chain is not supported at this time? I think that is fine and would be too much of a corner case. If somebody has such a config they could config the deeper proxies to forward (as-is) the header of the external facing proxy rather than rewrite it. |
|
@ayavilevich the assignment is the last statement in the code, so if an exception is raised while calculating the value to assign, the assignment itself is not done. I've updated the PR description with the breaking change that we will include in the release notes. |
|
I think that we should not default trusted proxies to the value of trusted networks. That seems like an unexpected coupling. It is better to do a breaking change. |
|
Thanks for all the work on this Colin, this looks great. |
I understand and agree that a breaking change is acceptable in some cases, but the way it breaks matters. If it breaks by "not working" that is one thing but if it breaks with an "authentication bypass", meaning anybody on the internet will be able to access a person's HA instance, that is another thing completely. You might be potentially creating a bigger security issue than we had in the first place. I have another suggestion on how to resolve backward compatibility without coupling the two fields.
|
Documents the setting added in home-assistant/core#15204
Documents the setting added in home-assistant/core#15204
* Use new trusted_proxies setting for X-Forwarded-For whitelist * Only use the last IP in the header Per Wikipedia (https://en.wikipedia.org/wiki/X-Forwarded-For#Format): > The last IP address is always the IP address that connects to the last proxy, > which means it is the most reliable source of information. * Add two additional tests * Ignore nonsense header values instead of failing
Breaking change: Anyone who has enabled
use_x_forwarded_forwill need to explicitly whitelist their proxy/proxies using the newtrusted_proxiessetting.This PR builds upon #15182 by making four changes:
trusted_proxiessetting exclusively for theX-Forwarded-ForwhitelistX-Forwarded-ForheaderX-Forwarded-Forheader values instead of issuing HTTP 500 errorsDescription:
New
trusted_proxiessettingPR #15182 fixed issues where
X-Forwarded-Forheaders were blindly trusted by HA. That PR used thetrusted_networkssetting (originally designed to bypass password authentication) to also determine whether the request was sent from a trusted proxy.While this did resolve the security issue, it also introduced a potential downside: If (for some reason) the HTTP requests originates from the same IP as the proxy server, then authentication will be bypassed. (Note that this does not affect traffic originating from outside the proxy server, which is the typical use case.)
For example, let's say you're running HA on your local machine with this configuration:
And let's say you've also got nginx running locally as your proxy. Well, nginx is going to send an
X-Forwarded-For: 127.0.0.1header, so that'll be your real IP, and that'll also match thetrusted_networksused by the auth middleware.Some users may need to set a wider range of trusted proxies without allowing proxied requests from those same IPs to bypass password authentication. This PR therefore adds a new
trusted_proxiessetting exclusively for theX-Forwarded-Forwhitelist to use.This change allows the user to trust a wide range of proxies (perhaps they're using a cloud proxy like Cloudflare with multiple IP ranges) while keeping the auth-bypass range extremely small.
See #15182 (comment) for further information.
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#5624
Use last IP in header
The
X-Forwarded-Forheader usually only contains a single IP. However, it's possible that some reverse proxies may respect an existing header and append the IP they detect at the end. According to Wikipedia:If multiple IPs are present, we should not trust any previous ones in the string since those come from potentially untrusted sources - only the last one is from our trusted proxy. The second commit in this PR therefore ensures that we only look at this IP.
(It may be possible to add support for checking those intermediate proxies in the future. However, because HA has previously only supported a single upstream proxy via the
use_x_forwarded_forfeature, we're doing the same here and via #15182 - just in a safer way.)Example entry for
configuration.yaml(if applicable):Let's pretend our home IP is
12.34.56.78and we're using proxying through Cloudflare. We don't want to be prompted for a password if we're connecting from home.Before:
If a malicious attacker is able to issue HTTP requests from within Cloudflare's infrastucture (maybe they hacked a CF server), those requests would bypass password authentication. Remember, this does NOT affect proxied traffic originating outside of Cloudflare.
After:
Checklist:
tox. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code does not interact with devices:
addedmodified to verify that the new code works.