-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
properly handle 'proxy-connection: close' header #6749
Conversation
AFAIK proxy-connection is not a spec header so I'm unclear if we should support it? @alyssawilk @PiotrSikora any thoughts here? |
From what I read
it's basically a "bug" in HTTP/1 which is supposed to be stripped for HTTP/2. Unless it's commonly supported by other OSS proxies I'd be inclined to relegate closing to a filter, though I'd be fine stripping it for HTTP/2. I think this PR as-is will result in goaway-drain for H2 which I think we definitely want to avoid. @securityinsanity can you give a bit more detail where you're running into this? |
It is a non-standard header, and essentially only supported in certain pieces of software (unfortunately http-parser supports it, which does all HTTP Parsing in envoy). To give a background as to what we ran into:
Essentially a spam bot was able to cause random requests to 400 (which has caused tons of problems). I'm happy to add in a config guard for HTTP/1.1 only, (if we want to strip it for HTTP/2). The main reason we filed this as a PR instead of developing our own filter is that we figured since Http-Parser supported it, and Envoy is built on that it should support it too. That being said it is unstandard so if it doesn't want to be supported we understand. |
@securityinsanity thanks for the detailed notes. I guess since http_parser supports this we should probably also support it at the higher layer. Agree with @alyssawilk though that we should only do this for HTTP/1.1. I see that this header was already known to Envoy, is it already being stripped for HTTP/2? |
@mattklein123 I don't see anywhere:
I'm happy to add the behaviour to this PR though. I'll get going on that now. |
Sorry I meant it's already in header_map.h. I'm not sure how/why it got there. |
Ah that I'm not sure about, git blame shows me you touched it last, though bringing it up does show:
I'm unfamiliar with that part of the codebase, is that perhaps stripping the headers for HTTP/2? |
The mutate call strips ProxyConnection for all response headers right before writing them to the client, so it sounds like we do what's "necessary". +1 to what Matt said that if http_parser thinks the connection is closed, Envoy should too - thanks for digging into this. I think you can scope the effect of your change pretty easily by exempting the close if protocol is Protocol::Http2 |
Sounds good, I'll do that and add in some tests when I get back from lunch. |
Looks like coverage failed again, and release failed for: |
Coverage is borked right now -we're working on it and hopefully we'll have
it sorted soon, but Matt also made it so it won't block merges in the
interim. So don't worry about getting that passing, and I'll check out the
code change regardless of coverage status tomorrow morning :-)
…On Tue, Apr 30, 2019 at 4:29 PM Eric ***@***.***> wrote:
Looks like coverage failed again, and release failed for:
//test/extensions/filters/http/fault:fault_filter_integration_test timing
out. This passes locally however. Is there a way for me to retrigger the
tests to get them to pass (and are these known problem areas?)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#6749 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AELALPI4OKN7DQ2CZZX4DKDPTCTZHANCNFSM4HJHG3AQ>
.
|
Thanks! Very much appreciated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great! Good catch on fixing up the connection pool and health checker as well.
FYI I believe we fixed coverage with #6761 so a master merge should get the build green now.
@@ -683,6 +683,12 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, | |||
Http::Headers::get().ConnectionValues.Close)) { | |||
state_.saw_connection_close_ = true; | |||
} | |||
if (protocol != Protocol::Http2 && !state_.saw_connection_close_ && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I believe from code inspection this will correctly remove the ProxyConnection header (which you dug up earlier) and later append the Connection: Close header, but I would like to make sure we have a regression test which both includes closing the connection (which you have) and making sure we swap headers (which I don't think this PR has?) to future-proof against various bits of code moving around.
You can either try to tweak your unit test to make sure the ProxyConnection is removed and Connection is added, or add an integration test to test/integration/http_integration.cc which does
auto response = sendRequestAndWaitForResponse(default_request_headers_, 0, proxy_connection_close, 0) and
EXPECT_THAT(response->headers(),
HeaderValueOf(Headers::get().ProxyConnection,
Http::Headers::get().ConnectionValues.Closed));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also optional, but it might be worth commenting that we support this non-standard header because http_parser does and we want to have consistent state.
I don't think we'll ever remove support once we add it even if we switch off of http_parser but it'd be nice to track.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe I took care of this by adding in the extra EXPECT
statement down below which seemed easier than writing an integration test. Although now re-reading this I'm unsure if you wanted to test sending a Proxy-Connection: close
and it getting replaced?
@@ -264,9 +264,12 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onResponseComplete() { | |||
break; | |||
} | |||
|
|||
if ((response_headers_->Connection() && | |||
if ((response_headers_->Connection() && protocol_ != Http::Protocol::Http2 && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to suggest a release note for this (as I assumed it changed the behavior for any H2 which was sending an H1 header) but according to the H2 spec the connection header is entirely illegal* and it turns out nghttp2 rejects it so this is a no-op.
https://http2.github.io/http2-spec/#rfc.section.8.1.2.2
I'd be inclined to leave this as-is without the Http::Protocol::Http2 check just on the theory we should keep the code simpler and not special case something illegal.
absl::EqualsIgnoreCase(response_headers_->Connection()->value().getStringView(), | ||
Http::Headers::get().ConnectionValues.Close)) || | ||
(response_headers_->ProxyConnection() && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think we want a "not H2" check here though.
currently envoy is only checking for the: `connection: close` header and not the: `proxy-connection: close` header. this presents a problem with HPE, because HPE marks a connection as closed when you specify `proxy-connection: close`. this can cause problems in a scenario where a load balancer is forwarding traffic to envoy which does not support `proxy-connection` header. in this case the load balancer will try to keep forwarding data onto envoy regardless of the fact that HPE think it's closed. thus causing a 400. this is a common scenario since `proxy-connection` is not standard. this commit fixes that by properly sending `connection: close` back in a response whenever we receive a: `proxy-connection: close`. this should properly get the proxy to close the connection as long as it is spec compliant. --- extra tests to validate: ontop of the unit tests the most basic full test you can run is: 1. Compile Envoy with this patch. 2. Supply it with: https://gist.github.com/SecurityInsanity/bd0807d7b8a4a96101106e9aab3a8518 (this is the most simple config I could think of). 3. Start a netcat session with the local envoy: ``` $ nc localhost 8443 GET /health_check HTTP/1.1 Host: localhost Proxy-Connection: close ``` You should get back a response with: `connection: close`. Signed-off-by: Eric <[email protected]>
Signed-off-by: Eric <[email protected]>
Signed-off-by: Eric <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep - while I'm always a fan of integration tests, I'm fine with you covering header removal in the unit test :-)
@PiotrSikora any thoughts from your end? If not I'm inclined to bump this to medium risk (just because it affects the HTTP data plane) and merge it later today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for digging into this, @securityinsanity! That's a pretty nasty way to reject legitimate requests.
This PR looks great, if we want to keep the fix within Envoy's codebase, but honestly, I'd much rather see this fixed in HTTP Parser, and simply ignore (i.e. pass-through) Proxy-Connection
in Envoy, instead of adding support for it, since no legitimate clients should be using this header nowadays, and other modern proxies don't have any special support for this header.
conn_pool_.test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); | ||
callbacks.outer_encoder_->encodeHeaders(TestHeaderMapImpl{}, true); | ||
|
||
// Response with 'connection: close' which should cause the connection to go away. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: proxy-connection: close
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
Signed-off-by: Eric <[email protected]>
FWIW on the legitimate clients front, our AWS Support rep did mention that many Microsoft products will send this header to get certain behaviors out of web servers. Unfortunately, they didn't expand on what "many" was. It does seem to line up (see the SQL Server Protocol for example: https://docs.microsoft.com/en-us/openspecs/sql_server_protocols/ms-ssas8/c2b9f128-76f3-48f9-b23d-6bf78516f842 ). Unsure if that's an actual use case envoy/nodejs cares about (it's certainly not one that impacts us, we just want our 400s to stop heh), or if Microsoft all sends "Connection" headers. Just figured I'd mention it for context. |
It seems that SQL Server sends |
@PiotrSikora would you be opposed to perhaps merging this now, and then I can file an issue on http-parser, and we can then remove it after http-parser has fixed it? It is completely blocking most things we do here, and while I understand that's not your problem it could be a good interim solution. |
I'm not against merging this PR, but it shouldn't be necessary (long-term), and if the consensus is that this header should be simply ignored, then we're adding workaround for http-parser and changing behavior that we're going to revert in O(weeks), which isn't great. I'll defer the decision to @alyssawilk and/or @mattklein123. |
I don't have a strong opinion. I'm OK with merging since that is going to be a lot faster than trying to fix http_parser unless we just pull the plug and fork it until we switch to something new (because I'm sure they are going to want a config option). @alyssawilk @envoyproxy/maintainers WDYT? We should at the very least open an http_parser issue and link here @securityinsanity |
Thanks Matt, I've created: nodejs/http-parser#477 which is fairly bare-bones, but can hopefully kick off the discussion with the http-parser maintainers. |
currently envoy is only checking for the: connection: close header and not the: proxy-connection: close header. this presents a problem with HPE, because HPE marks a connection as closed when you specify proxy-connection: close. this can cause problems in a scenario where a load balancer is forwarding traffic to envoy which does not support proxy-connection header. in this case the load balancer will try to keep forwarding data onto envoy regardless of the fact that HPE think it's closed. thus causing a 400. this is a common scenario since proxy-connection is not standard. this commit fixes that by properly sending connection: close back in a response whenever we receive a: proxy-connection: close. this should properly get the proxy to close the connection as long as it is spec compliant. Risk Level: Medium Testing: unit tests, manual integration tests. Docs Changes: None Release Notes: None Signed-off-by: Eric <[email protected]> Signed-off-by: Jeff Piazza <[email protected]>
Description:
currently envoy is only checking for the:
connection: close
headerand not the:
proxy-connection: close
header. this presents a problemwith HPE, because HPE marks a connection as closed when you specify
proxy-connection: close
.this can cause problems in a scenario where a load balancer is
forwarding traffic to envoy which does not support
proxy-connection
header. in this case the load balancer will try to keep forwarding
data onto envoy regardless of the fact that HPE think it's closed.
thus causing a 400. this is a common scenario since
proxy-connection
is not standard.
this commit fixes that by properly sending
connection: close
backin a response whenever we receive a:
proxy-connection: close
. thisshould properly get the proxy to close the connection as long as it
is spec compliant.
Risk Level: Medium
Testing:
ontop of the unit tests the most basic full test you can run is:
(this is the most simple config I could think of).
You should get back a response with:
connection: close
.Docs Changes: None
Release Notes: None
Signed-off-by: Eric [email protected]