diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index d9264ca66b664..62b2173e59f58 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -134,6 +134,13 @@ message BufferSettings { // The authorization request will be dispatched and no 413 HTTP error will be returned by the // filter. bool allow_partial_message = 2; + + // If true, the body sent to the external authorization service is set with raw bytes, it sets + // the :ref:`raw_body` + // field of HTTP request attribute context. Otherwise, :ref:` + // body` will be filled + // with UTF-8 string request body. + bool pack_as_bytes = 3; } // HttpService is used for raw HTTP communication between the filter and the authorization service. diff --git a/api/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto index 05ced92992581..9d9e3f313dc9a 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto @@ -134,6 +134,13 @@ message BufferSettings { // The authorization request will be dispatched and no 413 HTTP error will be returned by the // filter. bool allow_partial_message = 2; + + // If true, the body sent to the external authorization service is set with raw bytes, it sets + // the :ref:`raw_body` + // field of HTTP request attribute context. Otherwise, :ref:` + // body` will be filled + // with UTF-8 string request body. + bool pack_as_bytes = 3; } // HttpService is used for raw HTTP communication between the filter and the authorization service. diff --git a/api/envoy/service/auth/v3/attribute_context.proto b/api/envoy/service/auth/v3/attribute_context.proto index 3c4fe0af665ea..cdf3ee9f96e4b 100644 --- a/api/envoy/service/auth/v3/attribute_context.proto +++ b/api/envoy/service/auth/v3/attribute_context.proto @@ -97,7 +97,7 @@ message AttributeContext { // This message defines attributes for an HTTP request. // HTTP/1.x, HTTP/2, gRPC are all considered as HTTP requests. - // [#next-free-field: 12] + // [#next-free-field: 13] message HttpRequest { option (udpa.annotations.versioning).previous_message_type = "envoy.service.auth.v2.AttributeContext.HttpRequest"; @@ -145,6 +145,12 @@ message AttributeContext { // The HTTP request body. string body = 11; + + // The HTTP request body in bytes. This is used instead of + // :ref:`body ` when + // :ref:`pack_as_bytes ` + // is set to true. + bytes raw_body = 12; } // The source of a network activity, such as starting a TCP connection. diff --git a/api/envoy/service/auth/v4alpha/attribute_context.proto b/api/envoy/service/auth/v4alpha/attribute_context.proto index 24f728c7adef3..a1bf9c9c62cb2 100644 --- a/api/envoy/service/auth/v4alpha/attribute_context.proto +++ b/api/envoy/service/auth/v4alpha/attribute_context.proto @@ -97,7 +97,7 @@ message AttributeContext { // This message defines attributes for an HTTP request. // HTTP/1.x, HTTP/2, gRPC are all considered as HTTP requests. - // [#next-free-field: 12] + // [#next-free-field: 13] message HttpRequest { option (udpa.annotations.versioning).previous_message_type = "envoy.service.auth.v3.AttributeContext.HttpRequest"; @@ -145,6 +145,12 @@ message AttributeContext { // The HTTP request body. string body = 11; + + // The HTTP request body in bytes. This is used instead of + // :ref:`body ` when + // :ref:`pack_as_bytes ` + // is set to true. + bytes raw_body = 12; } // The source of a network activity, such as starting a TCP connection. diff --git a/docs/root/configuration/http/http_filters/ext_authz_filter.rst b/docs/root/configuration/http/http_filters/ext_authz_filter.rst index bd16c35bcc459..8dff0ce469500 100644 --- a/docs/root/configuration/http/http_filters/ext_authz_filter.rst +++ b/docs/root/configuration/http/http_filters/ext_authz_filter.rst @@ -22,7 +22,7 @@ configuration options at :ref:`HTTP filter `. Configuration Examples ------------------------------ +---------------------- A sample filter configuration for a gRPC authorization server: @@ -60,6 +60,38 @@ A sample filter configuration for a gRPC authorization server: # entire request. connect_timeout: 0.25s +.. note:: + + One of the features of this filter is to send HTTP request body to the configured gRPC + authorization server as part of the :ref:`check request + `. + + A sample configuration is as follows: + + .. code:: yaml + + http_filters: + - name: envoy.filters.http.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + grpc_service: + envoy_grpc: + cluster_name: ext-authz + with_request_body: + max_request_bytes: 1024 + allow_partial_message: true + pack_as_bytes: true + + Please note that by default :ref:`check request` + carries the HTTP request body as UTF-8 string and it fills the :ref:`body + ` field. To pack the request + body as raw bytes, it is needed to set :ref:`pack_as_bytes + ` field to + true. In effect to that, the :ref:`raw_body + ` + field will be set and :ref:`body + ` field will be empty. + A sample filter configuration for a raw HTTP authorization server: .. code-block:: yaml diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 1cc9028935919..ab2725511adfe 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -82,6 +82,7 @@ New Features * dynamic_forward_proxy: added :ref:`use_tcp_for_dns_lookups` option to use TCP for DNS lookups in order to match the DNS options for :ref:`Clusters`. * ext_authz filter: added support for emitting dynamic metadata for both :ref:`HTTP ` and :ref:`network ` filters. The emitted dynamic metadata is set by :ref:`dynamic metadata ` field in a returned :ref:`CheckResponse `. +* ext_authz filter: added support for sending :ref:`raw bytes as request body ` of a gRPC check request by setting :ref:`pack_as_bytes ` to true. * grpc-json: support specifying `response_body` field in for `google.api.HttpBody` message. * hds: added :ref:`cluster_endpoints_health ` to HDS responses, keeping endpoints in the same groupings as they were configured in the HDS specifier by cluster and locality instead of as a flat list. * hds: added :ref:`transport_socket_matches ` to HDS cluster health check specifier, so the existing match filter :ref:`transport_socket_match_criteria ` in the repeated field :ref:`health_checks ` has context to match against. This unblocks support for health checks over HTTPS and HTTP/2. diff --git a/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index 0c99cb6997f8c..f2f14a516b21f 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -133,6 +133,13 @@ message BufferSettings { // The authorization request will be dispatched and no 413 HTTP error will be returned by the // filter. bool allow_partial_message = 2; + + // If true, the body sent to the external authorization service is set with raw bytes, it sets + // the :ref:`raw_body` + // field of HTTP request attribute context. Otherwise, :ref:` + // body` will be filled + // with UTF-8 string request body. + bool pack_as_bytes = 3; } // HttpService is used for raw HTTP communication between the filter and the authorization service. diff --git a/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto b/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto index 05ced92992581..9d9e3f313dc9a 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto @@ -134,6 +134,13 @@ message BufferSettings { // The authorization request will be dispatched and no 413 HTTP error will be returned by the // filter. bool allow_partial_message = 2; + + // If true, the body sent to the external authorization service is set with raw bytes, it sets + // the :ref:`raw_body` + // field of HTTP request attribute context. Otherwise, :ref:` + // body` will be filled + // with UTF-8 string request body. + bool pack_as_bytes = 3; } // HttpService is used for raw HTTP communication between the filter and the authorization service. diff --git a/generated_api_shadow/envoy/service/auth/v3/attribute_context.proto b/generated_api_shadow/envoy/service/auth/v3/attribute_context.proto index 3c4fe0af665ea..cdf3ee9f96e4b 100644 --- a/generated_api_shadow/envoy/service/auth/v3/attribute_context.proto +++ b/generated_api_shadow/envoy/service/auth/v3/attribute_context.proto @@ -97,7 +97,7 @@ message AttributeContext { // This message defines attributes for an HTTP request. // HTTP/1.x, HTTP/2, gRPC are all considered as HTTP requests. - // [#next-free-field: 12] + // [#next-free-field: 13] message HttpRequest { option (udpa.annotations.versioning).previous_message_type = "envoy.service.auth.v2.AttributeContext.HttpRequest"; @@ -145,6 +145,12 @@ message AttributeContext { // The HTTP request body. string body = 11; + + // The HTTP request body in bytes. This is used instead of + // :ref:`body ` when + // :ref:`pack_as_bytes ` + // is set to true. + bytes raw_body = 12; } // The source of a network activity, such as starting a TCP connection. diff --git a/generated_api_shadow/envoy/service/auth/v4alpha/attribute_context.proto b/generated_api_shadow/envoy/service/auth/v4alpha/attribute_context.proto index 24f728c7adef3..a1bf9c9c62cb2 100644 --- a/generated_api_shadow/envoy/service/auth/v4alpha/attribute_context.proto +++ b/generated_api_shadow/envoy/service/auth/v4alpha/attribute_context.proto @@ -97,7 +97,7 @@ message AttributeContext { // This message defines attributes for an HTTP request. // HTTP/1.x, HTTP/2, gRPC are all considered as HTTP requests. - // [#next-free-field: 12] + // [#next-free-field: 13] message HttpRequest { option (udpa.annotations.versioning).previous_message_type = "envoy.service.auth.v3.AttributeContext.HttpRequest"; @@ -145,6 +145,12 @@ message AttributeContext { // The HTTP request body. string body = 11; + + // The HTTP request body in bytes. This is used instead of + // :ref:`body ` when + // :ref:`pack_as_bytes ` + // is set to true. + bytes raw_body = 12; } // The source of a network activity, such as starting a TCP connection. diff --git a/source/extensions/filters/common/ext_authz/check_request_utils.cc b/source/extensions/filters/common/ext_authz/check_request_utils.cc index 55847df1a946f..71752d23bff4a 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.cc +++ b/source/extensions/filters/common/ext_authz/check_request_utils.cc @@ -102,7 +102,7 @@ void CheckRequestUtils::setRequestTime(envoy::service::auth::v3::AttributeContex void CheckRequestUtils::setHttpRequest( envoy::service::auth::v3::AttributeContext::HttpRequest& httpreq, uint64_t stream_id, const StreamInfo::StreamInfo& stream_info, const Buffer::Instance* decoding_buffer, - const Envoy::Http::RequestHeaderMap& headers, uint64_t max_request_bytes) { + const Envoy::Http::RequestHeaderMap& headers, uint64_t max_request_bytes, bool pack_as_bytes) { httpreq.set_id(std::to_string(stream_id)); httpreq.set_method(getHeaderStr(headers.Method())); httpreq.set_path(getHeaderStr(headers.Path())); @@ -130,7 +130,15 @@ void CheckRequestUtils::setHttpRequest( const uint64_t length = std::min(decoding_buffer->length(), max_request_bytes); std::string data(length, 0); decoding_buffer->copyOut(0, length, &data[0]); - httpreq.set_body(std::move(data)); + + // This pack_as_bytes flag allows us to switch the content type (bytes or string) of "body" to + // be sent to the external authorization server without doing string encoding check (in this + // case UTF-8 check). + if (pack_as_bytes) { + httpreq.set_raw_body(std::move(data)); + } else { + httpreq.set_body(std::move(data)); + } // Add in a header to detect when a partial body is used. (*mutable_headers)[Http::Headers::get().EnvoyAuthPartialBody.get()] = @@ -141,10 +149,10 @@ void CheckRequestUtils::setHttpRequest( void CheckRequestUtils::setAttrContextRequest( envoy::service::auth::v3::AttributeContext::Request& req, const uint64_t stream_id, const StreamInfo::StreamInfo& stream_info, const Buffer::Instance* decoding_buffer, - const Envoy::Http::RequestHeaderMap& headers, uint64_t max_request_bytes) { + const Envoy::Http::RequestHeaderMap& headers, uint64_t max_request_bytes, bool pack_as_bytes) { setRequestTime(req, stream_info); setHttpRequest(*req.mutable_http(), stream_id, stream_info, decoding_buffer, headers, - max_request_bytes); + max_request_bytes, pack_as_bytes); } void CheckRequestUtils::createHttpCheck( @@ -152,7 +160,7 @@ void CheckRequestUtils::createHttpCheck( const Envoy::Http::RequestHeaderMap& headers, Protobuf::Map&& context_extensions, envoy::config::core::v3::Metadata&& metadata_context, - envoy::service::auth::v3::CheckRequest& request, uint64_t max_request_bytes, + envoy::service::auth::v3::CheckRequest& request, uint64_t max_request_bytes, bool pack_as_bytes, bool include_peer_certificate) { auto attrs = request.mutable_attributes(); @@ -163,10 +171,10 @@ void CheckRequestUtils::createHttpCheck( auto* cb = const_cast(callbacks); setAttrContextPeer(*attrs->mutable_source(), *cb->connection(), service, false, include_peer_certificate); - setAttrContextPeer(*attrs->mutable_destination(), *cb->connection(), "", true, + setAttrContextPeer(*attrs->mutable_destination(), *cb->connection(), EMPTY_STRING, true, include_peer_certificate); setAttrContextRequest(*attrs->mutable_request(), cb->streamId(), cb->streamInfo(), - cb->decodingBuffer(), headers, max_request_bytes); + cb->decodingBuffer(), headers, max_request_bytes, pack_as_bytes); // Fill in the context extensions and metadata context. (*attrs->mutable_context_extensions()) = std::move(context_extensions); diff --git a/source/extensions/filters/common/ext_authz/check_request_utils.h b/source/extensions/filters/common/ext_authz/check_request_utils.h index c8d165cd5200b..f0272d6741666 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.h +++ b/source/extensions/filters/common/ext_authz/check_request_utils.h @@ -46,6 +46,7 @@ class CheckRequestUtils { * check request. * @param request is the reference to the check request that will be filled up. * @param with_request_body when true, will add the request body to the check request. + * @param pack_as_bytes when true, will set the check request body as bytes. * @param include_peer_certificate whether to include the peer certificate in the check request. */ static void createHttpCheck(const Envoy::Http::StreamDecoderFilterCallbacks* callbacks, @@ -53,7 +54,8 @@ class CheckRequestUtils { Protobuf::Map&& context_extensions, envoy::config::core::v3::Metadata&& metadata_context, envoy::service::auth::v3::CheckRequest& request, - uint64_t max_request_bytes, bool include_peer_certificate); + uint64_t max_request_bytes, bool pack_as_bytes, + bool include_peer_certificate); /** * createTcpCheck is used to extract the attributes from the network layer and fill them up @@ -76,13 +78,13 @@ class CheckRequestUtils { const uint64_t stream_id, const StreamInfo::StreamInfo& stream_info, const Buffer::Instance* decoding_buffer, const Envoy::Http::RequestHeaderMap& headers, - uint64_t max_request_bytes); + uint64_t max_request_bytes, bool pack_as_bytes); static void setAttrContextRequest(envoy::service::auth::v3::AttributeContext::Request& req, const uint64_t stream_id, const StreamInfo::StreamInfo& stream_info, const Buffer::Instance* decoding_buffer, const Envoy::Http::RequestHeaderMap& headers, - uint64_t max_request_bytes); + uint64_t max_request_bytes, bool pack_as_bytes); static std::string getHeaderStr(const Envoy::Http::HeaderEntry* entry); static Envoy::Http::HeaderMap::Iterate fillHttpHeaders(const Envoy::Http::HeaderEntry&, void*); }; diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index a3ddccc497901..68dc777def022 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -61,7 +61,8 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers, Filters::Common::ExtAuthz::CheckRequestUtils::createHttpCheck( callbacks_, headers, std::move(context_extensions), std::move(metadata_context), - check_request_, config_->maxRequestBytes(), config_->includePeerCertificate()); + check_request_, config_->maxRequestBytes(), config_->packAsBytes(), + config_->includePeerCertificate()); ENVOY_STREAM_LOG(trace, "ext_authz filter calling authorization server", *callbacks_); state_ = State::Calling; diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 55e35d24709c7..7da1e0c8b3836 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -60,6 +60,7 @@ class FilterConfig { failure_mode_allow_(config.failure_mode_allow()), clear_route_cache_(config.clear_route_cache()), max_request_bytes_(config.with_request_body().max_request_bytes()), + pack_as_bytes_(config.with_request_body().pack_as_bytes()), status_on_error_(toErrorCode(config.status_on_error().code())), scope_(scope), runtime_(runtime), http_context_(http_context), filter_enabled_(config.has_filter_enabled() @@ -90,6 +91,8 @@ class FilterConfig { uint32_t maxRequestBytes() const { return max_request_bytes_; } + bool packAsBytes() const { return pack_as_bytes_; } + Http::Code statusOnError() const { return status_on_error_; } bool filterEnabled() { return filter_enabled_.has_value() ? filter_enabled_->enabled() : true; } @@ -132,6 +135,7 @@ class FilterConfig { const bool failure_mode_allow_; const bool clear_route_cache_; const uint32_t max_request_bytes_; + const bool pack_as_bytes_; const Http::Code status_on_error_; Stats::Scope& scope_; Runtime::Loader& runtime_; diff --git a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc index ef221d97407b6..69234ec8366d0 100644 --- a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc +++ b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc @@ -56,9 +56,9 @@ class CheckRequestUtilsTest : public testing::Test { auto metadata_val = MessageUtil::keyValueStruct("foo", "bar"); (*metadata_context.mutable_filter_metadata())["meta.key"] = metadata_val; - CheckRequestUtils::createHttpCheck(&callbacks_, request_headers, std::move(context_extensions), - std::move(metadata_context), request, false, - include_peer_certificate); + CheckRequestUtils::createHttpCheck( + &callbacks_, request_headers, std::move(context_extensions), std::move(metadata_context), + request, /*max_request_bytes=*/0, /*pack_as_bytes=*/false, include_peer_certificate); EXPECT_EQ("source", request.attributes().source().principal()); EXPECT_EQ("destination", request.attributes().destination().principal()); @@ -154,7 +154,8 @@ TEST_F(CheckRequestUtilsTest, BasicHttp) { expectBasicHttp(); CheckRequestUtils::createHttpCheck(&callbacks_, request_headers, Protobuf::Map(), - envoy::config::core::v3::Metadata(), request_, size, false); + envoy::config::core::v3::Metadata(), request_, size, + /*pack_as_bytes=*/false, /*include_peer_certificate=*/false); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_EQ(request_.attributes().request().http().headers().end(), @@ -175,7 +176,8 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithPartialBody) { expectBasicHttp(); CheckRequestUtils::createHttpCheck(&callbacks_, headers_, Protobuf::Map(), - envoy::config::core::v3::Metadata(), request_, size, false); + envoy::config::core::v3::Metadata(), request_, size, + /*pack_as_bytes=*/false, /*include_peer_certificate=*/false); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_EQ("true", request_.attributes().request().http().headers().at( @@ -193,7 +195,8 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBody) { expectBasicHttp(); CheckRequestUtils::createHttpCheck( &callbacks_, headers_, Protobuf::Map(), - envoy::config::core::v3::Metadata(), request_, buffer_->length(), false); + envoy::config::core::v3::Metadata(), request_, buffer_->length(), /*pack_as_bytes=*/false, + /*include_peer_certificate=*/false); ASSERT_EQ(buffer_->length(), request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, buffer_->length()), request_.attributes().request().http().body()); @@ -201,6 +204,48 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBody) { Http::Headers::get().EnvoyAuthPartialBody.get())); } +// Verify that check request object has all the request data and packed as bytes instead of UTF-8 +// string. +TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBodyPackAsBytes) { + Http::TestRequestHeaderMapImpl headers_; + envoy::service::auth::v3::CheckRequest request_; + + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); + EXPECT_CALL(*ssl_, uriSanLocalCertificate()) + .WillOnce(Return(std::vector{"destination"})); + + // Fill the buffer with non UTF-8 data. + uint8_t raw[2] = {0xc0, 0xc0}; + Buffer::OwnedImpl raw_buffer(raw, 2); + buffer_->drain(buffer_->length()); + buffer_->add(raw_buffer); + + expectBasicHttp(); + + // Setting pack_as_bytes as false and a string field with invalid UTF-8 data makes + // calling request_.SerializeToString() below to print an error message to stderr. Interestingly, + // request_.SerializeToString() still returns "true" when it is failed to serialize the data. + CheckRequestUtils::createHttpCheck( + &callbacks_, headers_, Protobuf::Map(), + envoy::config::core::v3::Metadata(), request_, buffer_->length(), /*pack_as_bytes=*/true, + /*include_peer_certificate=*/false); + + // TODO(dio): Find a way to test this without using function from testing::internal namespace. + testing::internal::CaptureStderr(); + std::string out; + ASSERT_TRUE(request_.SerializeToString(&out)); + ASSERT_EQ("", testing::internal::GetCapturedStderr()); + + // Non UTF-8 data sets raw_body field, instead of body field. + ASSERT_EQ(buffer_->length(), request_.attributes().request().http().raw_body().size()); + ASSERT_EQ(0, request_.attributes().request().http().body().size()); + + EXPECT_EQ(buffer_->toString().substr(0, buffer_->length()), + request_.attributes().request().http().raw_body()); + EXPECT_EQ("false", request_.attributes().request().http().headers().at( + Http::Headers::get().EnvoyAuthPartialBody.get())); +} + // Verify that createHttpCheck extract the proper attributes from the http request into CheckRequest // proto object. // Verify that the source certificate is not set by default. diff --git a/test/extensions/filters/http/ext_authz/config_test.cc b/test/extensions/filters/http/ext_authz/config_test.cc index 7a3f011032fef..f1beb38faaca2 100644 --- a/test/extensions/filters/http/ext_authz/config_test.cc +++ b/test/extensions/filters/http/ext_authz/config_test.cc @@ -97,6 +97,7 @@ TEST(HttpExtAuthzConfigTest, CorrectProtoHttp) { failure_mode_allow: true with_request_body: max_request_bytes: 100 + pack_as_bytes: true )EOF"; ExtAuthzFilterConfig factory; diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index a7016e1d9870f..f9a05671984be 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -483,13 +483,18 @@ TEST_F(HttpFilterTest, AuthWithRequestData) { max_request_bytes: 10 )EOF"); + ON_CALL(filter_callbacks_, decodingBuffer()).WillByDefault(Return(&data_)); prepareCheck(); + envoy::service::auth::v3::CheckRequest check_request; EXPECT_CALL(*client_, check(_, _, _, testing::A(), _)) - .WillOnce( - WithArgs<0>(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks) -> void { + .WillOnce(WithArgs<0, 2>( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest& check_param) -> void { request_callbacks_ = &callbacks; + check_request = check_param; }))); + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); data_.add("foo"); @@ -497,6 +502,50 @@ TEST_F(HttpFilterTest, AuthWithRequestData) { data_.add("bar"); EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, true)); EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(data_.length(), check_request.attributes().request().http().body().size()); + EXPECT_EQ(0, check_request.attributes().request().http().raw_body().size()); +} + +// Checks that the filter buffers the data and initiates the authorization request. +TEST_F(HttpFilterTest, AuthWithNonUtf8RequestData) { + InSequence s; + + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + with_request_body: + max_request_bytes: 10 + pack_as_bytes: true + )EOF"); + + ON_CALL(filter_callbacks_, decodingBuffer()).WillByDefault(Return(&data_)); + prepareCheck(); + + envoy::service::auth::v3::CheckRequest check_request; + EXPECT_CALL(*client_, check(_, _, _, testing::A(), _)) + .WillOnce(WithArgs<0, 2>( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest& check_param) -> void { + request_callbacks_ = &callbacks; + check_request = check_param; + }))); + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_, false)); + + // Use non UTF-8 data to fill up the decoding buffer. + uint8_t raw[1] = {0xc0}; + Buffer::OwnedImpl raw_buffer(raw, 1); + + data_.add(raw_buffer); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(data_, false)); + data_.add(raw_buffer); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, true)); + EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(0, check_request.attributes().request().http().body().size()); + EXPECT_EQ(data_.length(), check_request.attributes().request().http().raw_body().size()); } // Checks that filter does not buffer data on header-only request.