support multiple trusted proxies when processing XFF#2559
support multiple trusted proxies when processing XFF#2559alyssawilk merged 6 commits intoenvoyproxy:masterfrom brian-pane:trusted-proxy/2503
Conversation
Signed-off-by: Brian Pane <bpane@pinterest.com>
Signed-off-by: Brian Pane <bpane@pinterest.com>
|
The CI errors for Mac and TSAN seem to be due to unrelated causes. I opened issue #2561 for the TSAN error. |
…iest trusted remote address rather than immediate client address. Signed-off-by: Brian Pane <bpane@pinterest.com>
mattklein123
left a comment
There was a problem hiding this comment.
In general looks great to me. Some random comments. Thanks for the careful change here. This code scares the shit out of me so would prefer to have multiple reviews. @alyssawilk can you give some attention to this on Monday?
| // our peer to have already properly set XFF, etc. | ||
| Network::Address::InstanceConstSharedPtr final_remote_address; | ||
| bool single_xff_address; | ||
| auto xff_num_trusted_hops = config.xffNumTrustedHops(); |
There was a problem hiding this comment.
nit: prefer explicit type (not auto) for this type of code for clarity.
| single_xff_address = request_headers.ForwardedFor() == nullptr; | ||
| // If there are any trusted proxies in front of this Envoy instance (as indicated by | ||
| // the xff_num_trusted_hops configuration option), get the trusted client address | ||
| // from the XFF. |
There was a problem hiding this comment.
nit: extend comment to say something like "do this before we append to XFF" (to explain the subtraction, etc.)
| // the xff_num_trusted_hops configuration option), get the trusted client address | ||
| // from the XFF. | ||
| if (xff_num_trusted_hops > 0) { | ||
| auto ret = Utility::getLastAddressFromXFF(request_headers, xff_num_trusted_hops - 1); |
There was a problem hiding this comment.
nit: I think you could just assign to final_remote_address directly via (...).address_
| */ | ||
| static GetLastAddressFromXffInfo getLastAddressFromXFF(const Http::HeaderMap& request_headers); | ||
| static GetLastAddressFromXffInfo getLastAddressFromXFF(const Http::HeaderMap& request_headers, | ||
| uint32_t num_to_skip = 0); |
test/common/http/utility_test.cc
Outdated
| TestHeaderMapImpl request_headers{ | ||
| {"x-forwarded-for", fmt::format("{0}, {0}, {1}", first_address, second_address)}}; | ||
| {"x-forwarded-for", | ||
| fmt::format("{0}, {1}, {2}", first_address, second_address, third_address)}}; |
There was a problem hiding this comment.
nit: Can you just get rid of the fmt::format here and write out the header/strings directly? I think this just makes the test harder to read.
| return { | ||
| Network::Utility::parseInternetAddress(std::string(xff_string.data(), xff_string.size())), | ||
| last_comma == std::string::npos}; | ||
| last_comma == std::string::npos && num_to_skip == 0}; |
There was a problem hiding this comment.
Q: Do you plan on enhancing the internal address detection behavior in the case of trusted proxies? I think the logic we have now is not-optimal. (Well, it was not optimal to begin with, now it's much less optimal). I point this out because if we change that behavior it will have to be configured and we will have to be super careful.
There was a problem hiding this comment.
I can think of three approaches:
-
Add a configuration flag that changes the internal address detection to something like "the request is internal if and only if the trusted client address is an RFC1918/RFC4193 address."
-
When the new xff_num_trusted_hops is set, do the internal address detection based on the trusted client address. (And use the existing internal address logic if xff_num_trusted_hops is unset/zero.)
-
Leave the internal address detection unchanged.
Do you have a preference among those?
There was a problem hiding this comment.
I would just do (3) unless you need the behavior to be otherwise. I was just pointing it out as an open issue that might need to be worked on in the future.
There was a problem hiding this comment.
SGTM. Assuming the IP tagging filter operates on the trusted remote address, I'll just use that to differentiate internal from external.
There was a problem hiding this comment.
Blerg, I'm torn on this.
I think if we're adding support for multiple xff headers, "the right thing to do" would be to consider it trusted if the direct connected host and N hops were trusted.
I was going to say given Brian's not using Envoy's notion of internal and touching auth code is scary, we could just leave it as a TODO. But it'd be uglier if we need to come around and clean this up later when someone wants it as it'd require yet another config option to avoid suddenly marking requests as internal for Envoy users.
I keep coming back to the fact the concept of "is this internal" is generally purpose useful but so far from my sample set of 3 (pinterest, lyft, google) the methods of determining "this is internal" and "what to do about it" are sufficiently user-specific I'm tempted to say we make everyone do their own filter, and move the current Lyft code to a reference implementation filter which could maybe even assert xff_num_trusted_hops was only 0 to show it was not supported. @mattklein123 WDYT? Is this more trouble than it's worth?
Signed-off-by: Brian Pane <bpane@pinterest.com>
test/common/http/utility_test.cc
Outdated
| const std::string third_address = "10.0.0.1"; | ||
| TestHeaderMapImpl request_headers{ | ||
| {"x-forwarded-for", fmt::format("{0}, {0}, {1}", first_address, second_address)}}; | ||
| {"x-forwarded-for", first_address + ", " + second_address + ", " + third_address}}; |
There was a problem hiding this comment.
Sorry I actually meant remove the first_address, etc. variables and literally do:
{"x-forwarded-for", "129.0.2.10, 192.0.2.1, 10.0.0.1"}
:) But just a nit/preference if you feel otherwise.
| // our peer to have already properly set XFF, etc. | ||
| Network::Address::InstanceConstSharedPtr final_remote_address; | ||
| bool single_xff_address; | ||
| uint32_t xff_num_trusted_hops = config.xffNumTrustedHops(); |
mattklein123
left a comment
There was a problem hiding this comment.
LGTM other than nits. Will defer to @alyssawilk for further review/merge.
Signed-off-by: Brian Pane <bpane@pinterest.com>
Signed-off-by: Brian Pane <bpane@pinterest.com> Signed-off-by: Brian Pane <bpane@pinterest.com>
alyssawilk
left a comment
There was a problem hiding this comment.
Code LGTM insofar as it does what it says it does, and it's crazy well documented (yay!)
My concerns is I think we're adding more complexity to an already complex system, and even with great docs between the various combinations and permutations of use_remote_address and xff_number_trusted_hops, and inconsistency with how "trusted" works, I feel like any addition of complexity should come with some clean up :-)
Chatted with Brian offline and he had one thought for simplifying, which was only supporting xff_num_trusted_hops for nodes with use_remote_address==true which would at least remove the N vs N+1 hops confusion.
| return { | ||
| Network::Utility::parseInternetAddress(std::string(xff_string.data(), xff_string.size())), | ||
| last_comma == std::string::npos}; | ||
| last_comma == std::string::npos && num_to_skip == 0}; |
There was a problem hiding this comment.
Blerg, I'm torn on this.
I think if we're adding support for multiple xff headers, "the right thing to do" would be to consider it trusted if the direct connected host and N hops were trusted.
I was going to say given Brian's not using Envoy's notion of internal and touching auth code is scary, we could just leave it as a TODO. But it'd be uglier if we need to come around and clean this up later when someone wants it as it'd require yet another config option to avoid suddenly marking requests as internal for Envoy users.
I keep coming back to the fact the concept of "is this internal" is generally purpose useful but so far from my sample set of 3 (pinterest, lyft, google) the methods of determining "this is internal" and "what to do about it" are sufficiently user-specific I'm tempted to say we make everyone do their own filter, and move the current Lyft code to a reference implementation filter which could maybe even assert xff_num_trusted_hops was only 0 to show it was not supported. @mattklein123 WDYT? Is this more trouble than it's worth?
TBH I could easily see someone asking for this to work even when use_remote_address is false. My inclination is to keep it as is, but I don't feel that strongly about it. |
I think this is a great idea and something we should definitely consider. Please open a ticket to track. With that said, we absolutely cannot change the current behavior with causing chaos to not only Lyft but who knows who else (I think we all agree on that but just throwing it out there). As you point out, this is a) ridiculously complicated and b) everyone has a slightly different definition of what internal/external means. I think my vote is to move forward with @brian-pane change as-is and then consider any further changes as potentially moving into some type of filter/plug-in system. Thoughts? |
alyssawilk
left a comment
There was a problem hiding this comment.
Ok, my one holding point was I liked the simplicity of not having all 4 combinations of permutations of remote / xff but if you think it's worth keeping I'm otherwise fine.
@brian-pane can you open a issue ticket for clean up? You don't have to own it but we should at least note this is an area where help is wanted :-)
|
Ok, I've created issue 2590 for the follow-up. |
* Update envoy-wasm sha Signed-off-by: Lizan Zhou <lizan@tetrate.io> * fix due to ABI change * remove compile db Signed-off-by: Lizan Zhou <lizan@tetrate.io> * fix for envoyproxy/envoy-wasm#216 Signed-off-by: Lizan Zhou <lizan@tetrate.io> * fix ci * roll forward Signed-off-by: Lizan Zhou <lizan@tetrate.io>
Signed-off-by: Snow Pettersen <snowp@lyft.com> Signed-off-by: JP Simard <jp@jpsim.com>
Signed-off-by: Snow Pettersen <snowp@lyft.com> Signed-off-by: JP Simard <jp@jpsim.com>
Description:
If the xff_num_trusted_hops config option is set to a number greater than zero, trust the specified number of additional addresses at the end of the
X-Forwarded-Forrequest header.Risk Level: High
(because this change interacts with the internal/external request security model)
Testing: New test cases included
Docs Changes: #479
Release Notes: Included
Fixes: #2503
API Changes: #459
Signed-off-by: Brian Pane bpane@pinterest.com