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 37 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>`;
// if not set, this is overridden by any HCM :ref:`stream_error_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.

I think you can leave out the "if not set" because the sentence above makes it clear that otherwise the HCM value is used.

// <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 @@ -14,7 +14,7 @@ Minor Behavior Changes
* compressor: always insert `Vary` headers for compressible resources even if it's decided not to compress a response due to incompatible `Accept-Encoding` value. The `Vary` header needs to be inserted to let a caching proxy in front of Envoy know that the requested resource still can be served with compression applied.
* decompressor: headers-only requests were incorrectly not advertising accept-encoding when configured to do so. This is now fixed.
* 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.

12 changes: 12 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 absl::optional<bool> streamErrorOnInvalidHttpMessage() const PURE;
};

/**
Expand Down Expand Up @@ -386,6 +392,12 @@ 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 set and true, the HTTP/1.1 connection is left open (where possible)
// - if set and false, the HTTP/1.1 connection is terminated
// - if not set, fall back to the HCM behaviour on invalid HTTP messaging
Copy link
Member

Choose a reason for hiding this comment

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

Similar comment here about HCM. This is generic for upstream and downstream. Where does the upstream default come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The new HTTP/1.1 config introduced here works in tandem with the newly introduced stream_override_on_invalid_http_message API for the HCM, and it's a noop when used in the context of an upstream where there is no HCM. Maybe this should be better clarified in the comments?!

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I think it would be good to call this out more in the comments and the proto documentation. The proto is a generic message that is used for both upstream and downstream, so I think it would be good to clarify that it only applies right now for downstream?

absl::optional<bool> stream_error_on_invalid_http_message_;
};

/**
Expand Down
16 changes: 14 additions & 2 deletions source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ void recordLatestDataFilter(const typename FilterList<T>::iterator current_filte
}
}

bool streamErrorOnInvalidHttpMessage(
bool hcm_stream_error_on_invalid_http_message,
const absl::optional<bool> override_stream_error_on_invalid_http_message) {
if (override_stream_error_on_invalid_http_message.has_value()) {
return override_stream_error_on_invalid_http_message.value();
} else {
return hcm_stream_error_on_invalid_http_message;
}
}

} // namespace

ConnectionManagerStats ConnectionManagerImpl::generateStats(const std::string& prefix,
Expand Down Expand Up @@ -1578,8 +1588,10 @@ void ConnectionManagerImpl::ActiveStream::sendLocalReply(
// 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 &&
!streamErrorOnInvalidHttpMessage(
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to runtime guard this twice? Can we remove the first if condition? Also, I haven't been tracking all of these changes, and you probably already discussed this with Alyssa, but is this the only place this needs to be checked? I would have naively assumed there are more places this is needed but maybe not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The runtime guard here is needed because it affects HTTP/1.1 specifically, and the other place where it's checked affects HTTP/2 instead. As far as I understand, prior to #11714, we were sending errors in two places (codec and HCM), however after that change we now send errors only in the HCM. Not sure if we should check in the other code path as well (see #11884 (comment)).

connection_manager_.config_.streamErrorOnInvalidHttpMessaging(),
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,
absl::optional<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); }

absl::optional<bool> streamErrorOnInvalidHttpMessage() const override {
return stream_error_on_invalid_http_message_;
}

private:
bool started_response_{};
const absl::optional<bool> stream_error_on_invalid_http_message_;
};

/**
Expand Down Expand Up @@ -463,8 +470,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,
absl::optional<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); }

absl::optional<bool> streamErrorOnInvalidHttpMessage() const override {
return stream_error_on_invalid_http_message_;
}

private:
bool started_response_{};
const absl::optional<bool> stream_error_on_invalid_http_message_;
};

/**
Expand Down Expand Up @@ -436,8 +443,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_;

absl::optional<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_;
absl::optional<bool> stream_error_on_invalid_http_message_;

absl::optional<bool> streamErrorOnInvalidHttpMessage() const override {
return stream_error_on_invalid_http_message_;
}
};

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

if (config.has_override_stream_error_on_invalid_http_message()) {
ret.stream_error_on_invalid_http_message_ =
config.override_stream_error_on_invalid_http_message().value();
}

return ret;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase,
Http::Http1StreamEncoderOptionsOptRef http1StreamEncoderOptions() override {
return absl::nullopt;
}
absl::optional<bool> streamErrorOnInvalidHttpMessage() const override { return absl::nullopt; }

// Http::Stream
void resetStream(Http::StreamResetReason reason) override;
Expand Down
Loading