Skip to content

Commit c3ab9e5

Browse files
ext_authz: respect keep_empty_value in HeaderValueOption (envoyproxy#45103)
1 parent 968337a commit c3ab9e5

4 files changed

Lines changed: 60 additions & 10 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixed a bug where ``keep_empty_value`` in ``HeaderValueOption`` was not respected by
2+
the ext_authz filter. Empty-valued headers are now dropped by default. This behavior
3+
can be reverted by setting the runtime guard
4+
``envoy.reloadable_features.ext_authz_respect_keep_empty_value`` to ``false``.

source/common/runtime/runtime_features.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ RUNTIME_GUARD(envoy_reloadable_features_enable_new_dns_implementation);
5050
FALSE_RUNTIME_GUARD(envoy_reloadable_features_enable_lrs_server_self_ads);
5151
RUNTIME_GUARD(envoy_reloadable_features_enable_new_query_param_present_match_behavior);
5252
RUNTIME_GUARD(envoy_reloadable_features_ext_authz_http_client_retries_respect_user_retry_on);
53+
RUNTIME_GUARD(envoy_reloadable_features_ext_authz_respect_keep_empty_value);
5354
// Ignore the automated "remove this flag" issue: we should keep this for 1 year. Confirm with
5455
// @yanjunxiang-google before removing.
5556
RUNTIME_GUARD(envoy_reloadable_features_ext_proc_fail_close_spurious_resp);

source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ void copyHeaderFieldIntoResponse(
2020
ResponsePtr& response,
2121
const Protobuf::RepeatedPtrField<envoy::config::core::v3::HeaderValueOption>& headers) {
2222
for (const auto& header : headers) {
23+
if (Runtime::runtimeFeatureEnabled(
24+
"envoy.reloadable_features.ext_authz_respect_keep_empty_value") &&
25+
header.header().value().empty() && !header.keep_empty_value()) {
26+
continue;
27+
}
2328
if (header.append().value()) {
2429
response->headers_to_append.emplace_back(header.header().key(), header.header().value());
2530
} else {
@@ -33,6 +38,11 @@ void copyOkResponseMutations(ResponsePtr& response,
3338
copyHeaderFieldIntoResponse(response, ok_response.headers());
3439

3540
for (const auto& header : ok_response.response_headers_to_add()) {
41+
if (Runtime::runtimeFeatureEnabled(
42+
"envoy.reloadable_features.ext_authz_respect_keep_empty_value") &&
43+
header.header().value().empty() && !header.keep_empty_value()) {
44+
continue;
45+
}
3646
if (header.has_append()) {
3747
if (header.append().value()) {
3848
response->response_headers_to_add.emplace_back(header.header().key(),

test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -635,10 +635,8 @@ TEST_F(ExtAuthzGrpcClientTest, AuthorizationOkUpstreamHeaderMutations) {
635635
span_);
636636
}
637637

638-
// TODO(https://github.com/envoyproxy/envoy/issues/45003)
639-
// This test ensures the default (buggy) behavior remains unchanged since
640-
// existing Envoy deployments may be relying on the default (buggy) behavior
641-
TEST_F(ExtAuthzGrpcClientTest, AuthorizationOkWithKeepEmptyValueIgnored) {
638+
// This test ensures keep_empty_value is respected in ext_authz responses.
639+
TEST_F(ExtAuthzGrpcClientTest, AuthorizationOkWithKeepEmptyValueRespected) {
642640
initialize();
643641

644642
envoy::service::auth::v3::CheckResponse check_response;
@@ -654,12 +652,10 @@ TEST_F(ExtAuthzGrpcClientTest, AuthorizationOkWithKeepEmptyValueIgnored) {
654652
)EOF",
655653
check_response);
656654

657-
// If keep_empty_value were respected, the header "x-keep-empty" should be DROPPED
658-
// because its value is empty. However, the current implementation ignores this
659-
// flag and blindly adds the empty header to the headers_to_set vector.
655+
// keep_empty_value is false and value is empty, so the header should be dropped.
660656
auto expected_authz_response = Response{
661657
.status = CheckStatus::OK,
662-
.headers_to_set = UnsafeHeaderVector{{"x-keep-empty", ""}},
658+
.headers_to_set = UnsafeHeaderVector{},
663659
.status_code = Http::Code::OK,
664660
.grpc_status = Grpc::Status::WellKnownGrpcStatus::Ok,
665661
};
@@ -672,8 +668,47 @@ TEST_F(ExtAuthzGrpcClientTest, AuthorizationOkWithKeepEmptyValueIgnored) {
672668
client_->onCreateInitialMetadata(headers);
673669

674670
EXPECT_CALL(span_, setTag(Eq("ext_authz_status"), Eq("ext_authz_ok")));
675-
EXPECT_CALL(request_callbacks_, onComplete_(WhenDynamicCastTo<ResponsePtr&>(
676-
AuthzOkResponse(expected_authz_response))));
671+
EXPECT_CALL(request_callbacks_, onComplete_(testing::_))
672+
.WillOnce(testing::Invoke([&expected_authz_response](ResponsePtr& response) {
673+
EXPECT_TRUE(TestCommon::compareHeaderVector(response->headers_to_set,
674+
expected_authz_response.headers_to_set));
675+
}));
676+
client_->onSuccess(std::make_unique<envoy::service::auth::v3::CheckResponse>(check_response),
677+
span_);
678+
}
679+
680+
TEST_F(ExtAuthzGrpcClientTest, AuthorizationOkWithKeepEmptyValueTrue) {
681+
initialize();
682+
envoy::service::auth::v3::CheckResponse check_response;
683+
TestUtility::loadFromYaml(R"EOF(
684+
status:
685+
code: 0
686+
ok_response:
687+
headers:
688+
- header:
689+
key: x-keep-empty
690+
value: ""
691+
keep_empty_value: true
692+
)EOF",
693+
check_response);
694+
// keep_empty_value is true and value is empty, so the header should be kept.
695+
auto expected_authz_response = Response{
696+
.status = CheckStatus::OK,
697+
.headers_to_set = UnsafeHeaderVector{{"x-keep-empty", ""}},
698+
.status_code = Http::Code::OK,
699+
.grpc_status = Grpc::Status::WellKnownGrpcStatus::Ok,
700+
};
701+
envoy::service::auth::v3::CheckRequest request;
702+
expectCallSend(request);
703+
client_->check(request_callbacks_, request, Tracing::NullSpan::instance(), stream_info_);
704+
Http::TestRequestHeaderMapImpl headers;
705+
client_->onCreateInitialMetadata(headers);
706+
EXPECT_CALL(span_, setTag(Eq("ext_authz_status"), Eq("ext_authz_ok")));
707+
EXPECT_CALL(request_callbacks_, onComplete_(testing::_))
708+
.WillOnce(testing::Invoke([&expected_authz_response](ResponsePtr& response) {
709+
EXPECT_TRUE(TestCommon::compareHeaderVector(response->headers_to_set,
710+
expected_authz_response.headers_to_set));
711+
}));
677712
client_->onSuccess(std::make_unique<envoy::service::auth::v3::CheckResponse>(check_response),
678713
span_);
679714
}

0 commit comments

Comments
 (0)