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
Copy http headers to ThreadContext strictly #45945
Conversation
Previous behavior while copying HTTP headers to the ThreadContext, would allow multiple HTTP headers with the same name, handling only the first occurence and disregarding the rest of the values. This can be confusing when dealing with multiple Headers as it is not obvious which value is read and which ones are silently dropped. According to RFC-7230, a client must not send multiple header fields with the same field name in a HTTP message, unless the entire field value for this header is defined as a comma separated list or this specific header is a well-known exception. This commits changes the behavior in order to be more compliant to the aforementioned RFC by requiring the classes that implement ActionPlugin to declare if a header can be multi-valued or not when registering this header to be copied over to the ThreadContext in ActionPlugin#getRestHeaders. If the header is allowed to be multivalued, then all such headers are read from the HTTP request and their values get concatenated in a comma-separated string. If the header is not allowed to be multivalued, and the HTTP request contains multiple such Headers with different values, the request is rejected with a 400 status.
Pinging @elastic/es-core-infra |
For 7.x, we can refrain from making any breaking changes and minimize the changes required by only introducing This would make the assumption that all registered headers are meant to be single-valued, but I think it is a fair assumption for all currently registered headers in all plugins, since we would only read the first of the values either way. |
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java
Show resolved
Hide resolved
@elasticmachine run elasticsearch-ci/1 |
@jasontedor I dont mean to rush things, just doing a round on my open PRs. |
} | ||
} catch (IllegalStateException e){ |
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.
} catch (IllegalStateException e){ | |
} catch (IllegalStateException e) { |
public Collection<RestHeaderDefinition> getRestHeaders() { | ||
return Arrays.asList( | ||
new RestHeaderDefinition(CustomRealm.USER_HEADER, true), | ||
new RestHeaderDefinition(CustomRealm.PW_HEADER, true)); |
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.
Shouldn't these be false
?
I know it doesn't matter for testing purposes, but we point people to this as an example, and I think we want the default expaction for security examples to be single-valued headers.
@jasontedor ping for a review 🙏 |
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.
@jkakavas I'm so sorry I lost track of this one, it's unacceptable.
Overall the approach looks good, I left a comment for consideration.
} | ||
} catch (IllegalStateException e) { |
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.
It's too bad we have to use an unchecked exception for flow control here. Can we look for another way? For example, one suggestion would be to pull the multi-valued validation into this method instead of holding it in RestRequest#getSingleValuedHeader.
Or, if this is not palatable, or if we can't find another way, can RestRequest#getSingleValuedHeader
at least declare this exception in its Javadocs?
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 good, I left one more observation, and a minor nit.
createSimpleErrorResponse(channel, BAD_REQUEST, "multiple values for single-valued header [" + name + "].")); | ||
return; | ||
} else { | ||
threadContext.putHeader(name, headerValues.stream().distinct().collect(Collectors.joining(","))); |
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 effectively going to collect allocate a set to collect the header values twice in case multi-value headers are not allowed. Once to collect the header values into a set to see if its size is one, and then again (when the set is size one) to do the trivial join. Since we always need to collect the distinct values (either to check if we should fail the request, or put the header in the context), I think we should refactor this slightly to avoid collecting twice and all the allocations that come with it.
String httpHeader = request.header(key); | ||
if (httpHeader != null) { | ||
threadContext.putHeader(key, httpHeader); | ||
for (RestHeaderDefinition restHeader : headersToCopy) { |
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.
The loop iterator can be final
.
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.
That looks great @jkakavas. Thanks for the iterations.
@elasticmachine run elasticsearch-ci/packaging-sample-matrix |
packaging sample was actually successful : https://elasticsearch-ci.elastic.co/job/elastic+elasticsearch+pull-request+packaging-tests-sample/8348/
|
@elasticmachine run elasticsearch-ci/packaging-sample |
:( |
@elasticmachine run elasticsearch-ci/packaging-sample |
Previous behavior while copying HTTP headers to the ThreadContext, would allow multiple HTTP headers with the same name, handling only the first occurrence and disregarding the rest of the values. This can be confusing when dealing with multiple Headers as it is not obvious which value is read and which ones are silently dropped. According to RFC-7230, a client must not send multiple header fields with the same field name in a HTTP message, unless the entire field value for this header is defined as a comma separated list or this specific header is a well-known exception. This commits changes the behavior in order to be more compliant to the aforementioned RFC by assuming all HTTP headers are single valued and throwing an error if a header with many values is sent. Backport of elastic#45945 without the breaking change in ActionPlugin#getRestHeaders
Previous behavior while copying HTTP headers to the ThreadContext, would allow multiple HTTP headers with the same name, handling only the first occurrence and disregarding the rest of the values. This can be confusing when dealing with multiple Headers as it is not obvious which value is read and which ones are silently dropped. According to RFC-7230, a client must not send multiple header fields with the same field name in a HTTP message, unless the entire field value for this header is defined as a comma separated list or this specific header is a well-known exception. This commits changes the behavior in order to be more compliant to the aforementioned RFC by requiring the classes that implement ActionPlugin to declare if a header can be multi-valued or not when registering this header to be copied over to the ThreadContext in ActionPlugin#getRestHeaders. If the header is allowed to be multivalued, then all such headers are read from the HTTP request and their values get concatenated in a comma-separated string. If the header is not allowed to be multivalued, and the HTTP request contains multiple such Headers with different values, the request is rejected with a 400 status.
Previous behavior while copying HTTP headers to the ThreadContext, would allow multiple HTTP headers with the same name, handling only the first occurrence and disregarding the rest of the values. This can be confusing when dealing with multiple Headers as it is not obvious which value is read and which ones are silently dropped. According to RFC-7230, a client must not send multiple header fields with the same field name in a HTTP message, unless the entire field value for this header is defined as a comma separated list or this specific header is a well-known exception. This commits changes the behavior in order to be more compliant to the aforementioned RFC by requiring the classes that implement ActionPlugin to declare if a header can be multi-valued or not when registering this header to be copied over to the ThreadContext in ActionPlugin#getRestHeaders. If the header is allowed to be multivalued, then all such headers are read from the HTTP request and their values get concatenated in a comma-separated string. If the header is not allowed to be multivalued, and the HTTP request contains multiple such Headers with different values, the request is rejected with a 400 status.
Previous behavior while copying HTTP headers to the ThreadContext,
would allow multiple HTTP headers with the same name, handling only
the first occurrence and disregarding the rest of the values. This
can be confusing when dealing with multiple Headers as it is not
obvious which value is read and which ones are silently dropped.
According to RFC-7230, a client must not send multiple header fields
with the same field name in a HTTP message, unless the entire field
value for this header is defined as a comma separated list or this
specific header is a well-known exception.
This commits changes the behavior in order to be more compliant to
the aforementioned RFC by requiring the classes that implement
ActionPlugin to declare if a header can be multi-valued or not when
registering this header to be copied over to the ThreadContext in
ActionPlugin#getRestHeaders.
If the header is allowed to be multivalued, then all such headers
are read from the HTTP request and their values get concatenated in
a comma-separated string.
If the header is not allowed to be multivalued, and the HTTP
request contains multiple such Headers with different values, the
request is rejected with a 400 status.