Skip to content
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

http: stream error on invalid HTTP messaging for HTTP/1 #11884

Merged
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ee7cb0d
Add config for behavior on HTTP invalid message to HTTP1 options. Als…
rulex123 Jul 3, 2020
b04b98e
Correct typo
rulex123 Jul 3, 2020
46768be
Shadow API
rulex123 Jul 3, 2020
d1bf85d
Extend codec to return behavior on invalid HTTP messaging
rulex123 Jul 9, 2020
2d7e93d
Update API documentation
rulex123 Jul 9, 2020
86031f0
Add TODO
rulex123 Jul 9, 2020
397e369
Add utility function that determines behavior on invalid HTTP messagi…
rulex123 Jul 9, 2020
c139ff1
Format code
rulex123 Jul 9, 2020
2d6cac8
Fix format
rulex123 Jul 13, 2020
d30e854
Add const
rulex123 Jul 17, 2020
aa421fd
Plug logic into HCM
rulex123 Jul 17, 2020
9718246
Format
rulex123 Jul 17, 2020
cc33ccf
Add unit test for HCM logic
rulex123 Jul 19, 2020
cab6751
Merge branch 'master' into behavior-on-invalid-http-message
rulex123 Jul 19, 2020
00814d2
Add unit tests for HCM
rulex123 Jul 20, 2020
7f61bdf
Update API to remove hide-implementation annotation
rulex123 Jul 20, 2020
908bdfc
Remove changes from v2 API
rulex123 Jul 20, 2020
e5239e8
Code format headaches
rulex123 Jul 22, 2020
4b2f72a
Revisit API docs
rulex123 Jul 22, 2020
922bb6f
Add unit test for codec HTTP/1.1
rulex123 Jul 24, 2020
9dec35a
Add unit test for codec HTTP/2
rulex123 Jul 24, 2020
5fb9be3
Integration tests for HTTP/1.1
rulex123 Jul 27, 2020
83cbadf
Integration test
rulex123 Jul 27, 2020
c9c55bc
Formatting, again
rulex123 Jul 27, 2020
a31323f
HTTP/3 stub out method for response encoder
rulex123 Jul 28, 2020
354c08b
Update HCM API docs and release notes
rulex123 Jul 29, 2020
07c3ee5
Address code review feedback
rulex123 Jul 31, 2020
5e1b95e
Move code from utility class to anon namespace
rulex123 Jul 31, 2020
eec3bb2
Remove TODO
rulex123 Jul 31, 2020
f1787af
Fix typo
rulex123 Jul 31, 2020
2fe6fbc
Merge branch 'master' into behavior-on-invalid-http-message
rulex123 Jul 31, 2020
447b719
Fix CI
rulex123 Aug 2, 2020
c1d456a
Merge branch 'master' into behavior-on-invalid-http-message
rulex123 Aug 3, 2020
025dbc4
Merge branch 'master' into behavior-on-invalid-http-message
rulex123 Aug 12, 2020
30154d7
Address code review feedback
rulex123 Aug 12, 2020
f99724d
Fix formatting
rulex123 Aug 12, 2020
a35bada
More code review feedback
rulex123 Aug 17, 2020
b037f64
Merge branch 'master' into behavior-on-invalid-http-message
rulex123 Aug 24, 2020
16414a0
Have logic that determines stream error on codec instantiation
rulex123 Aug 26, 2020
4bb2a86
Unit test for init logic of stream_error
rulex123 Aug 26, 2020
1410972
Update doc
rulex123 Aug 26, 2020
5e63612
Address feedback and fix CI
rulex123 Aug 27, 2020
85e57c4
Address feedback
rulex123 Aug 28, 2020
3dd2c3d
Merge branch 'master' into behavior-on-invalid-http-message
rulex123 Aug 28, 2020
464a5c7
Fix unit test
rulex123 Aug 28, 2020
e663b45
Fix test
rulex123 Aug 28, 2020
d92640d
Merge branch 'master' into behavior-on-invalid-http-message
rulex123 Sep 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion api/envoy/config/core/v3/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ message HttpProtocolOptions {
HeadersWithUnderscoresAction headers_with_underscores_action = 5;
}

// [#next-free-field: 6]
// [#next-free-field: 7]
message Http1ProtocolOptions {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.core.Http1ProtocolOptions";
Expand Down Expand Up @@ -157,6 +157,16 @@ message Http1ProtocolOptions {
// - Not a response to a HEAD request.
// - The content length header is not present.
bool enable_trailers = 5;

// Allows invalid HTTP messaging. When this option is false, then Envoy will terminate
// HTTP/1.1 connections upon receiving an invalid HTTP message. However,
// when this option is true, then Envoy will leave the HTTP/1.1 connection
// open where possible.
// If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging
// <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.stream_error_on_invalid_http_message>`;
// this is overridden by any HCM :ref:`stream_error_on_invalid_http_messaging
// <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.stream_error_on_invalid_http_message>`, which defaults to false.
google.protobuf.BoolValue override_stream_error_on_invalid_http_message = 6;
}

// [#next-free-field: 15]
Expand Down
12 changes: 11 additions & 1 deletion api/envoy/config/core/v4alpha/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,8 @@ message HttpConnectionManager {
// company-internal mesh) and false when receiving untrusted traffic (edge deployments).
//
// If different behaviors for invalid_http_message for HTTP/1 and HTTP/2 are
// desired, one *must* use the new HTTP/2 option
// desired, one should use the new HTTP/1 option :ref:`override_stream_error_on_invalid_http_message
// <envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.override_stream_error_on_invalid_http_message>` or the new HTTP/2 option
// :ref:`override_stream_error_on_invalid_http_message
// <envoy_v3_api_field_config.core.v3.Http2ProtocolOptions.override_stream_error_on_invalid_http_message>`
// *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Minor Behavior Changes
* http: added :ref:`contains <envoy_api_msg_type.matcher.StringMatcher>` a new string matcher type which matches if the value of the string has the substring mentioned in contains matcher.
* http: added :ref:`contains <envoy_api_msg_route.HeaderMatcher>` a new header matcher type which matches if the value of the header has the substring mentioned in contains matcher.
* http: added :ref:`headers_to_add <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.ResponseMapper.headers_to_add>` to :ref:`local reply mapper <config_http_conn_man_local_reply>` to allow its users to add/append/override response HTTP headers to local replies.
* http: added HCM level configuration of :ref:`error handling on invalid messaging <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.stream_error_on_invalid_http_message>` which substantially changes Envoy's behavior when encountering invalid HTTP/1.1 defaulting to closing the connection instead of allowing reuse. This can temporarily be reverted by setting `envoy.reloadable_features.hcm_stream_error_on_invalid_message` to false, or permanently reverted by setting the :ref:`HCM option <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.stream_error_on_invalid_http_message>` to true to restore prior HTTP/1.1 beavior and setting the *new* HTTP/2 configuration :ref:`override_stream_error_on_invalid_http_message <envoy_v3_api_field_config.core.v3.Http2ProtocolOptions.override_stream_error_on_invalid_http_message>` to false to retain prior HTTP/2 behavior.
* http: added HCM level configuration of :ref:`error handling on invalid messaging <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.stream_error_on_invalid_http_message>` which substantially changes Envoy's behavior when encountering invalid HTTP/1.1 defaulting to closing the connection instead of allowing reuse. This can temporarily be reverted by setting `envoy.reloadable_features.hcm_stream_error_on_invalid_message` to false, or permanently reverted by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message <envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.override_stream_error_on_invalid_http_message>` to true to restore prior HTTP/1.1 behavior (i.e. connection isn't terminated) and to retain prior HTTP/2 behavior (i.e. connection is terminated).
* http: changed Envoy to send error headers and body when possible. This behavior may be temporarily reverted by setting `envoy.reloadable_features.allow_response_for_timeout` to false.
* http: changed empty trailers encoding behavior by sending empty data with ``end_stream`` true (instead of sending empty trailers) for HTTP/2. This behavior can be reverted temporarily by setting runtime feature ``envoy.reloadable_features.http2_skip_encoding_empty_trailers`` to false.
* http: clarified and enforced 1xx handling. Multiple 100-continue headers are coalesced when proxying. 1xx headers other than {100, 101} are dropped.
Expand Down
12 changes: 11 additions & 1 deletion generated_api_shadow/envoy/config/core/v3/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion generated_api_shadow/envoy/config/core/v4alpha/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions include/envoy/http/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ class ResponseEncoder : public virtual StreamEncoder {
* @param trailers supplies the trailers to encode.
*/
virtual void encodeTrailers(const ResponseTrailerMap& trailers) PURE;

/**
* Indicates whether invalid HTTP messaging should be handled with a stream error or a connection
* error; if value is missing, then HCM drives behaviour on invalid HTTP messaging.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update comment to remove "if value is missing, then HCM drives behaviour on invalid HTTP messaging."

*/
virtual bool streamErrorOnInvalidHttpMessage() const PURE;
};

/**
Expand Down Expand Up @@ -386,6 +392,11 @@ struct Http1Settings {

// How header keys should be formatted when serializing HTTP/1.1 headers.
HeaderKeyFormat header_key_format_{HeaderKeyFormat::Default};

// Behaviour on invalid HTTP messaging:
// - if true, the HTTP/1.1 connection is left open (where possible)
// - if false, the HTTP/1.1 connection is terminated
bool stream_error_on_invalid_http_message_{false};
};

/**
Expand Down
4 changes: 2 additions & 2 deletions source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1170,8 +1170,8 @@ void ConnectionManagerImpl::ActiveStream::onLocalReply(Code code) {
// The BadRequest error code indicates there has been a messaging error.
if (Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.hcm_stream_error_on_invalid_message") &&
!connection_manager_.config_.streamErrorOnInvalidHttpMessaging() &&
code == Http::Code::BadRequest && connection_manager_.codec_->protocol() < Protocol::Http2) {
code == Http::Code::BadRequest && connection_manager_.codec_->protocol() < Protocol::Http2 &&
!response_encoder_->streamErrorOnInvalidHttpMessage()) {
state_.saw_connection_close_ = true;
}
}
Expand Down
16 changes: 12 additions & 4 deletions source/common/http/http1/codec_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,10 @@ class StreamEncoderImpl : public virtual StreamEncoder,
*/
class ResponseEncoderImpl : public StreamEncoderImpl, public ResponseEncoder {
public:
ResponseEncoderImpl(ConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter)
: StreamEncoderImpl(connection, header_key_formatter) {}
ResponseEncoderImpl(ConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter,
bool stream_error_on_invalid_http_message)
: StreamEncoderImpl(connection, header_key_formatter),
stream_error_on_invalid_http_message_(stream_error_on_invalid_http_message) {}

bool startedResponse() { return started_response_; }

Expand All @@ -133,8 +135,13 @@ class ResponseEncoderImpl : public StreamEncoderImpl, public ResponseEncoder {
void encodeHeaders(const ResponseHeaderMap& headers, bool end_stream) override;
void encodeTrailers(const ResponseTrailerMap& trailers) override { encodeTrailersBase(trailers); }

bool streamErrorOnInvalidHttpMessage() const override {
return stream_error_on_invalid_http_message_;
}

private:
bool started_response_{};
const bool stream_error_on_invalid_http_message_;
};

/**
Expand Down Expand Up @@ -462,8 +469,9 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl {
* An active HTTP/1.1 request.
*/
struct ActiveRequest {
ActiveRequest(ConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter)
: response_encoder_(connection, header_key_formatter) {}
ActiveRequest(ServerConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter)
: response_encoder_(connection, header_key_formatter,
connection.codec_settings_.stream_error_on_invalid_http_message_) {}

HeaderString request_url_;
RequestDecoder* request_decoder_{};
Expand Down
17 changes: 13 additions & 4 deletions source/common/http/http1/codec_impl_legacy.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,10 @@ class StreamEncoderImpl : public virtual StreamEncoder,
class ResponseEncoderImpl : public StreamEncoderImpl, public ResponseEncoder {
public:
ResponseEncoderImpl(ConnectionImpl& connection,
Http::Http1::HeaderKeyFormatter* header_key_formatter)
: StreamEncoderImpl(connection, header_key_formatter) {}
Http::Http1::HeaderKeyFormatter* header_key_formatter,
bool stream_error_on_invalid_http_message)
: StreamEncoderImpl(connection, header_key_formatter),
stream_error_on_invalid_http_message_(stream_error_on_invalid_http_message) {}

bool startedResponse() { return started_response_; }

Expand All @@ -136,8 +138,13 @@ class ResponseEncoderImpl : public StreamEncoderImpl, public ResponseEncoder {
void encodeHeaders(const ResponseHeaderMap& headers, bool end_stream) override;
void encodeTrailers(const ResponseTrailerMap& trailers) override { encodeTrailersBase(trailers); }

bool streamErrorOnInvalidHttpMessage() const override {
return stream_error_on_invalid_http_message_;
}

private:
bool started_response_{};
const bool stream_error_on_invalid_http_message_;
};

/**
Expand Down Expand Up @@ -435,8 +442,10 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl {
* An active HTTP/1.1 request.
*/
struct ActiveRequest {
ActiveRequest(ConnectionImpl& connection, Http::Http1::HeaderKeyFormatter* header_key_formatter)
: response_encoder_(connection, header_key_formatter) {}
ActiveRequest(ServerConnectionImpl& connection,
Http::Http1::HeaderKeyFormatter* header_key_formatter)
: response_encoder_(connection, header_key_formatter,
connection.codec_settings_.stream_error_on_invalid_http_message_) {}

HeaderString request_url_;
RequestDecoder* request_decoder_{};
Expand Down
4 changes: 4 additions & 0 deletions source/common/http/http2/codec_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,10 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log

RequestDecoder* request_decoder_{};
absl::variant<RequestHeaderMapPtr, RequestTrailerMapPtr> headers_or_trailers_;

bool streamErrorOnInvalidHttpMessage() const override {
return parent_.stream_error_on_invalid_http_messaging_;
}
};

using ServerStreamImplPtr = std::unique_ptr<ServerStreamImpl>;
Expand Down
9 changes: 8 additions & 1 deletion source/common/http/http2/codec_impl_legacy.h
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,9 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
*/
struct ServerStreamImpl : public StreamImpl, public ResponseEncoder {
ServerStreamImpl(ConnectionImpl& parent, uint32_t buffer_limit)
: StreamImpl(parent, buffer_limit), headers_or_trailers_(RequestHeaderMapImpl::create()) {}
: StreamImpl(parent, buffer_limit), headers_or_trailers_(RequestHeaderMapImpl::create()) {
stream_error_on_invalid_http_message_ = parent.stream_error_on_invalid_http_messaging_;
}

// StreamImpl
void submitHeaders(const std::vector<nghttp2_nv>& final_headers,
Expand Down Expand Up @@ -391,6 +393,11 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log

RequestDecoder* request_decoder_{};
absl::variant<RequestHeaderMapPtr, RequestTrailerMapPtr> headers_or_trailers_;
bool stream_error_on_invalid_http_message_;

bool streamErrorOnInvalidHttpMessage() const override {
return stream_error_on_invalid_http_message_;
}
};

using ServerStreamImplPtr = std::unique_ptr<ServerStreamImpl>;
Expand Down
19 changes: 19 additions & 0 deletions source/common/http/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,25 @@ Utility::parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOptions&
return ret;
}

Http1Settings
Utility::parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOptions& config,
bool hcm_stream_error_set,
const Protobuf::BoolValue& hcm_stream_error) {
Http1Settings ret = parseHttp1Settings(config);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function just return the override_stream_error if set, and if not returns the HCM value no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, isn't it the case that when this function is called the HCM value hasn't been initialised to its default value yet (in case of no explicit configuration)? See

stream_error_on_invalid_http_messaging_(
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, stream_error_on_invalid_http_message, false)),

I guess we could move that initialisation so that it happens earlier, and then simplify this function as per your comment. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I see what your concern is, but I believe (and you can unit test) that the default value for BoolValue is false, and the default for stream_error_on_invalid_http_messaging_ is false, so you should just be able to set the default to config.override_stream_error_on_invalid_http_message().value() even if it's not set.


if (config.has_override_stream_error_on_invalid_http_message()) {
// override_stream_error_on_invalid_http_message, if set, takes precedence over any HCM
// stream_error_on_invalid_http_message
ret.stream_error_on_invalid_http_message_ =
config.override_stream_error_on_invalid_http_message().value();
} else if (hcm_stream_error_set) {
// HCM, if set, overrides override_stream_error_on_invalid_http_message
ret.stream_error_on_invalid_http_message_ = hcm_stream_error.value();
}

return ret;
}

void Utility::sendLocalReply(const bool& is_reset, StreamDecoderFilterCallbacks& callbacks,
const LocalReplyData& local_reply_data) {
sendLocalReply(
Expand Down
4 changes: 4 additions & 0 deletions source/common/http/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ bool isWebSocketUpgradeRequest(const RequestHeaderMap& headers);
*/
Http1Settings parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOptions& config);

Http1Settings parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOptions& config,
bool hcm_stream_error_set,
const Protobuf::BoolValue& hcm_stream_error);

struct EncodeFunctions {
// Function to rewrite locally generated response.
std::function<void(ResponseHeaderMap& response_headers, Code& code, std::string& body,
Expand Down
Loading