diff --git a/api/envoy/config/filter/http/ext_authz/v2/ext_authz.proto b/api/envoy/config/filter/http/ext_authz/v2/ext_authz.proto index b430fe93a519f..4c9b4d9c82897 100644 --- a/api/envoy/config/filter/http/ext_authz/v2/ext_authz.proto +++ b/api/envoy/config/filter/http/ext_authz/v2/ext_authz.proto @@ -52,6 +52,8 @@ message ExtAuthz { bool use_alpha = 4 [deprecated = true]; // Enables filter to buffer the client request body and send it within the authorization request. + // A ``x-envoy-auth-partial-body: false|true`` metadata header will be added to the authorization + // request message indicating if the body data is partial. BufferSettings with_request_body = 5; // Clears route cache in order to allow the external authorization service to correctly affect diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 6e6d6a89942b5..75e3946e48cb2 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -7,6 +7,7 @@ Version history * dubbo_proxy: support the :ref:`Dubbo proxy filter `. * eds: added support to specify max time for which endpoints can be used :ref:`gRPC filter `. * event: added :ref:`loop duration and poll delay statistics `. +* ext_authz: added a `x-envoy-auth-partial-body` metadata header set to `false|true` indicating if there is a partial body sent in the authorization request message. * ext_authz: added option to `ext_authz` that allows the filter clearing route cache. * http: mitigated a race condition with the :ref:`delayed_close_timeout` where it could trigger while actively flushing a pending write buffer for a downstream connection. * redis: added :ref:`prefix routing ` to enable routing commands based on their key's prefix to different upstream. diff --git a/source/common/http/headers.h b/source/common/http/headers.h index 590b96541b3ae..38403792c545e 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -36,6 +36,7 @@ class HeaderValues { const LowerCaseString Cookie{"cookie"}; const LowerCaseString Date{"date"}; const LowerCaseString EnvoyAttemptCount{"x-envoy-attempt-count"}; + const LowerCaseString EnvoyAuthPartialBody{"x-envoy-auth-partial-body"}; const LowerCaseString EnvoyDegraded{"x-envoy-degraded"}; const LowerCaseString EnvoyDownstreamServiceCluster{"x-envoy-downstream-service-cluster"}; const LowerCaseString EnvoyDownstreamServiceNode{"x-envoy-downstream-service-node"}; 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 44b87f00b8bd8..e9b45bf17d9ea 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.cc +++ b/source/extensions/filters/common/ext_authz/check_request_utils.cc @@ -107,12 +107,15 @@ void CheckRequestUtils::setHttpRequest( auto mutable_headers = httpreq.mutable_headers(); headers.iterate( [](const Envoy::Http::HeaderEntry& e, void* ctx) { - Envoy::Protobuf::Map* - mutable_headers = static_cast< - Envoy::Protobuf::Map*>( - ctx); - (*mutable_headers)[std::string(e.key().getStringView())] = - std::string(e.value().getStringView()); + // Skip any client EnvoyAuthPartialBody header, which could interfere with internal use. + if (e.key().getStringView() != Http::Headers::get().EnvoyAuthPartialBody.get()) { + Envoy::Protobuf::Map* + mutable_headers = + static_cast*>(ctx); + (*mutable_headers)[std::string(e.key().getStringView())] = + std::string(e.value().getStringView()); + } return Envoy::Http::HeaderMap::Iterate::Continue; }, mutable_headers); @@ -124,6 +127,10 @@ void CheckRequestUtils::setHttpRequest( std::string data(length, 0); buffer->copyOut(0, length, &data[0]); 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()] = + length != buffer->length() ? "true" : "false"; } } 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 4adcde6276a33..be0d077cf7c66 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 @@ -75,17 +75,24 @@ TEST_F(CheckRequestUtilsTest, BasicTcp) { // Verify that createHttpCheck's dependencies are invoked when it's called. // Verify that check request object has no request data. +// Verify that a client supplied EnvoyAuthPartialBody will not affect the +// CheckRequest call. TEST_F(CheckRequestUtilsTest, BasicHttp) { const uint64_t size = 0; - Http::HeaderMapImpl headers_; envoy::service::auth::v2::CheckRequest request_; + // A client supplied EnvoyAuthPartialBody header should be ignored. + Http::TestHeaderMapImpl request_headers{{Http::Headers::get().EnvoyAuthPartialBody.get(), "1"}}; + ExpectBasicHttp(); - CheckRequestUtils::createHttpCheck(&callbacks_, headers_, + CheckRequestUtils::createHttpCheck(&callbacks_, request_headers, Protobuf::Map(), request_, size); 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(), + request_.attributes().request().http().headers().find( + Http::Headers::get().EnvoyAuthPartialBody.get())); } // Verify that check request object has only a portion of the request data. @@ -100,6 +107,8 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithPartialBody) { request_, size); 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( + Http::Headers::get().EnvoyAuthPartialBody.get())); } // Verify that check request object has all the request data. @@ -114,6 +123,8 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBody) { ASSERT_EQ(buffer_->length(), request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, buffer_->length()), request_.attributes().request().http().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