-
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
conn_manager: allow to remove port from host header #10960
Conversation
i will fix failed builds/ signoff the diff. for now just waiting for everything else to complete (to make sure that there is nothing else to fix) |
Please add test cases for authority headers with IPv4/IPv6 addresses |
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. While we wait for review from the API maintainers, I provided a couple of quick comments.
// Without setting this option, incoming requests with Host `example:443` will not match against | ||
// route with `domains` match set to `example`. Defaults to `false`. Note that port removal is not part | ||
// of `HTTP spec <https://tools.ietf.org/html/rfc3986>` and is provided for convenience. | ||
bool remove_port = 38; |
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 think the name of this field should be more specific. Something like strip_host_port
. Another thing to consider is how this interacts with auto host rewrite and other host-manipulation features.
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. yeah. naming is hard. i will wait for other comments and will address yours in next iteration.
as for auto host - from my understanding auto host is a part of Route component of the envoy. and this port stripping modifies host header before we hit it (see "RouteShouldUseNormalizedHost" unittest)
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 will be honest that my initial gut on this one is to not strip the port, but to instead add a routing option that would ignore port during route matching. I think the latter would be the most natural way of solving #886. However, I see your point about doing stripping once and then not having to worry about upstream proxies.
I would definitely like to hear from @alyssawilk and @PiotrSikora on this one.
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 prefer moving to a routing option.
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.
as Matt mentioned, matching in routing option would solve #866 (which was bonus point in this diff). but wont allow to simplify configuration for multiple layers of proxies and backends behind them (even if envoy on 2nd layer would ignore port matching, backend behind it wont; that would increas complexity of migrating from other proxies (nginx as example) to envoy).
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 thought we already removed the port when it matched the scheme, i.e. an http://foo.com:80 and https://foo.com:443 but apparently I'm misremembering? Anyway I'd prefer this as a routing option. I'm OK with a configuration guarded removal iff the port matches the listening port - if they don't match removing it feels sketchy.
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.
on matched the scheme. (unless it was change in last few days):
curl -k -v https://localhost/ -H 'Host:localhost'
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 7
< content-type: text/plain
< date: Tue, 28 Apr 2020 19:22:16 GMT
< server: envoy
<
curl -k -v https://localhost/ -H 'Host:localhost:443'
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< date: Tue, 28 Apr 2020 19:23:06 GMT
< server: envoy
< content-length: 0
<
part of envoy's test configuration which we are hitting in this example:
- name: local_service
domains: ["localhost"]
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.
as for the matching port - i'm not that familiar with this codepath. is it even possible to get listener object (and therefor it's port) from connection manager and/or route object?
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.
ok, so seems it is possible to get NetworkConnection from ActiveStream, and therefor local port. now the question is - do you want to make it part of the route object? (and therefor force users to match on "xxx:443" in domains (in this case this diff make little sense, and new one should be made), or you are ok w/ it being part of conn manager (but with removing port only if it is equal to the listeners port)?
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.
In a perfect world I think we should have both options, route matching with port stripping, as well as this option to strip port IFF the port matches the bound port (per @alyssawilk), but it's fine if you only want to do the latter in this PR.
source/common/http/path_utility.h
Outdated
@@ -19,6 +19,8 @@ class PathUtil { | |||
// Merges two or more adjacent slashes in path part of URI into one. | |||
// Requires the Path header be present. | |||
static void mergeSlashes(RequestHeaderMap& headers); | |||
// Remove port part from Host/authority header | |||
static void removePortsFromHost(RequestHeaderMap& headers); |
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 don't think this belongs in PathUtil. There's at least one other authority-related method in header_utility.h/cc's HeaderUtility class.
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, especially the thorough unit testing in RemovePortsFromHost :-)
@@ -506,6 +506,14 @@ message HttpConnectionManager { | |||
// | |||
// 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. | |||
RequestIDExtension request_id_extension = 36; | |||
|
|||
// Determines if port part should be removed from Host/authority header before any processing |
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.
mind changing Host -> host here and below?
I know Host is pretty standard for HTTP/1.1 but Envoy lowercases anyway and I think it's more consistent with other API docs.
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.
sure.
@@ -742,6 +742,18 @@ const Network::Connection* ConnectionManagerImpl::ActiveStream::connection() { | |||
return &connection_manager_.read_callbacks_->connection(); | |||
} | |||
|
|||
uint32_t ConnectionManagerImpl::ActiveStream::localPort() { | |||
auto conn = connection(); | |||
if (conn == nullptr) { |
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.
For sure looking at connection it must be a valid pointer as it's returning a pointer to a reference member.
I think it'd be fine to change both of these to asserts - I think if we create an HTTPConnectionManager with non-valid address we've violated earlier invariants.
@@ -673,6 +673,9 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>, | |||
void onStreamMaxDurationReached(); | |||
bool hasCachedRoute() { return cached_route_.has_value() && cached_route_.value(); } | |||
|
|||
// return local port of the connection |
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.
Super nitty, but
->
Return local port of the connection.
If you could check the comments below too that'd be awesome.
void ConnectionManagerUtility::maybeNormalizeHost(RequestHeaderMap& request_headers, | ||
const ConnectionManagerConfig& config, | ||
uint32_t port) { | ||
if (!request_headers.Host()) { |
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.
At the point we call this, we've either added a default host for HTTP/1.0 or sent a local reply and will not reach this point (HCM 850)
I don't mind you making it resilient to changes in code order, but if you're going to leave this in please make sure it's unit tested.
// Without setting this option, incoming requests with Host `example:443` will not match against | ||
// route with `domains` match set to `example`. Defaults to `false`. Note that port removal is not part | ||
// of `HTTP spec <https://tools.ietf.org/html/rfc3986>` and is provided for convenience. | ||
bool strip_host_port = 38; |
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.
optional, maybe something about the matching in the naming? strip_matching_host_port?
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.
Looking great - just a few more comment nits and you're good to go!
@@ -427,7 +427,7 @@ class ConnectionManagerConfig { | |||
/** | |||
* @return if the HttpConnectionManager should remove ports from Host/authority header |
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 think there's still some Host -> host changes to track down :-)
@@ -506,6 +506,14 @@ message HttpConnectionManager { | |||
// | |||
// 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. | |||
RequestIDExtension request_id_extension = 36; | |||
|
|||
// Determines if port part should be removed from host/authority header before any processing | |||
// of request by HTTP filters or routing. Port would be removed only if it is equal to the listener's |
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.
Port -> The port
and I'd change "connect" to CONNECT again just for consistency.
@@ -506,6 +506,14 @@ message HttpConnectionManager { | |||
// | |||
// 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. | |||
RequestIDExtension request_id_extension = 36; | |||
|
|||
// Determines if port part should be removed from host/authority header before any processing |
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.
really small nit but port -> the port ?
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.
@mattklein123 up for API sign off?
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.
Nice! Few small comments from a quick skim. Thank you!
/wait
// of request by HTTP filters or routing. The port would be removed only if it is equal to the listener's | ||
// local port and request method is not CONNECT. This affects the upstream host header as well. | ||
// Without setting this option, incoming requests with host `example:443` will not match against | ||
// route with `domains` match set to `example`. Defaults to `false`. Note that port removal is not part |
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 needs a release note. Also, do you mind ref linking fields in here where you can? For example domains.
@@ -742,6 +742,16 @@ const Network::Connection* ConnectionManagerImpl::ActiveStream::connection() { | |||
return &connection_manager_.read_callbacks_->connection(); | |||
} | |||
|
|||
uint32_t ConnectionManagerImpl::ActiveStream::localPort() { | |||
auto conn = connection(); | |||
ASSERT(conn != nullptr); |
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: generally we don't do asserts like this since it will crash in an obvious way on the next line. I would just remove the conn variable.
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
1. renaming remove_port to strip_host_port 2. stripping port only when it is equal to listener's local port Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
changes: 1. renaming strip_host_port to strip_matching_... 2. renaming shouldRemovePort to shouldRemoveMatchingPort 3. do not remove port if method is "connect" 4. removing some unnecessary checks Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
addressing nits/comments on spelling Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
* removed ASSERT from localPort helper * added ref into prot description * added ref to release notes * rebase Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
…p_patch Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
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.
Awesome thank you. Just a small doc nit and let's ship!
/wait
@@ -24,6 +24,7 @@ Changes | |||
Can be reverted temporarily by setting runtime feature `envoy.reloadable_features.fix_upgrade_response` to false. | |||
* http: remove legacy connection pool code and their runtime features: `envoy.reloadable_features.new_http1_connection_pool_behavior` and | |||
`envoy.reloadable_features.new_http2_connection_pool_behavior`. | |||
* http connection manager: added :ref:`stripping port from host header <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.strip_matching_host_port>` support. |
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.
s/http connection manager/http
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
forget to format. will do another commit |
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
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!
/azp run envoy-presubmit |
Azure Pipelines successfully started running 1 pipeline(s). |
/azp run envoy-presubmit |
Azure Pipelines successfully started running 1 pipeline(s). |
Sorry looks like you lost the merge race. :( Can you merge master again? /wait |
…p_patch Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
Signed-off-by: Nikita V. Shirokov <tehnerd@tehnerd.com>
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!
Commit Message:
add an api option/conn manager feature which would allow to remove port part from Host header (e.g. would transform
example:443
toexample
. this would simplify domain's matching inside virtual host as well as would not require explicit matching on "domain:port" in upstream proxies.Additional Description:
in production we see a lot of external requests from third party libraries, which are setting port part even for requests toward common port (443). w/o this diff on envoy side workaround was to match both on "example.com" and "example.com:443" in domains configuration. and to do this on every layer of proxies. as well as modifying application, which was running behind last level envoy, to do the same.
this diff would allow to set up remove_port option on first level envoy, and everywhere just to match on "example.com"
as a side effect it should also help to resolve #886
Risk Level:
LOW
Testing:
new unit tests + manual testing
Docs Changes:
inline in proto file