diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2961db159eb2..eeac91e7bf35 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -47,6 +47,10 @@ minor_behavior_changes: change: | Enable obsolete line folding in BalsaParser (for behavior parity with http-parser, the previously used HTTP/1 parser). +- area: proxy status + change: | + Add more conversion in the proxy status utility. It can be disabled by the runtime guard + ``envoy.reloadable_features.proxy_status_mapping_more_core_response_flags``. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 8ea3e31f731b..28bd09a6162d 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -75,6 +75,7 @@ RUNTIME_GUARD(envoy_reloadable_features_oauth_make_token_cookie_httponly); RUNTIME_GUARD(envoy_reloadable_features_oauth_use_standard_max_age_value); RUNTIME_GUARD(envoy_reloadable_features_oauth_use_url_encoding); RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout); +RUNTIME_GUARD(envoy_reloadable_features_proxy_status_mapping_more_core_response_flags); RUNTIME_GUARD(envoy_reloadable_features_proxy_status_upstream_request_timeout); RUNTIME_GUARD(envoy_reloadable_features_quic_fix_filter_manager_uaf); // Ignore the automated "remove this flag" issue: we should keep this for 1 year. Confirm with diff --git a/source/common/stream_info/utility.cc b/source/common/stream_info/utility.cc index 4045a45981b6..6ef029ef1cfe 100644 --- a/source/common/stream_info/utility.cc +++ b/source/common/stream_info/utility.cc @@ -440,15 +440,44 @@ ProxyStatusUtils::fromStreamInfo(const StreamInfo& stream_info) { return ProxyStatusError::ConnectionTimeout; } return ProxyStatusError::HttpResponseTimeout; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::LocalReset)) { - return ProxyStatusError::ConnectionTimeout; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamRemoteReset)) { - return ProxyStatusError::ConnectionTerminated; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionFailure)) { - return ProxyStatusError::ConnectionRefused; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionTermination)) { - return ProxyStatusError::ConnectionTerminated; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamOverflow)) { + } + + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.proxy_status_mapping_more_core_response_flags")) { + if (stream_info.hasResponseFlag(CoreResponseFlag::DurationTimeout)) { + return ProxyStatusError::ConnectionTimeout; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::LocalReset)) { + return ProxyStatusError::ConnectionTimeout; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamRemoteReset)) { + return ProxyStatusError::ConnectionTerminated; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionFailure)) { + return ProxyStatusError::ConnectionRefused; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UnauthorizedExternalService)) { + return ProxyStatusError::ConnectionRefused; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionTermination)) { + return ProxyStatusError::ConnectionTerminated; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::OverloadManager)) { + return ProxyStatusError::ConnectionLimitReached; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::DropOverLoad)) { + return ProxyStatusError::ConnectionLimitReached; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::FaultInjected)) { + return ProxyStatusError::HttpRequestError; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::DownstreamConnectionTermination)) { + return ProxyStatusError::ConnectionTerminated; + } + } else { + if (stream_info.hasResponseFlag(CoreResponseFlag::LocalReset)) { + return ProxyStatusError::ConnectionTimeout; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamRemoteReset)) { + return ProxyStatusError::ConnectionTerminated; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionFailure)) { + return ProxyStatusError::ConnectionRefused; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionTermination)) { + return ProxyStatusError::ConnectionTerminated; + } + } + + if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamOverflow)) { return ProxyStatusError::ConnectionLimitReached; } else if (stream_info.hasResponseFlag(CoreResponseFlag::NoRouteFound)) { return ProxyStatusError::DestinationNotFound; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 7f9f1f897d52..bd4b458d40f1 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -4430,6 +4430,43 @@ TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetailsAndResponseCode) { EXPECT_EQ(altered_headers->getStatusValue(), "504"); } +TEST_F(ProxyStatusTest, PopulateUnauthorizedProxyStatus) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_mapping_more_core_response_flags", "true"}}); + proxy_status_config_ = std::make_unique(); + proxy_status_config_->set_remove_details(false); + + initialize(); + + const ResponseHeaderMap* altered_headers = sendRequestWith( + 403, StreamInfo::CoreResponseFlag::UnauthorizedExternalService, /*details=*/"bar"); + + ASSERT_TRUE(altered_headers); + ASSERT_TRUE(altered_headers->ProxyStatus()); + EXPECT_EQ(altered_headers->getProxyStatusValue(), + "custom_server_name; error=connection_refused; details=\"bar; UAEX\""); + EXPECT_EQ(altered_headers->getStatusValue(), "403"); +} + +TEST_F(ProxyStatusTest, NoPopulateUnauthorizedProxyStatus) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_mapping_more_core_response_flags", "false"}}); + proxy_status_config_ = std::make_unique(); + proxy_status_config_->set_remove_details(false); + + initialize(); + + const ResponseHeaderMap* altered_headers = sendRequestWith( + 403, StreamInfo::CoreResponseFlag::UnauthorizedExternalService, /*details=*/"bar"); + + ASSERT_TRUE(altered_headers); + ASSERT_FALSE(altered_headers->ProxyStatus()); + EXPECT_EQ(altered_headers->getProxyStatusValue(), ""); + EXPECT_EQ(altered_headers->getStatusValue(), "403"); +} + TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetails) { TestScopedRuntime scoped_runtime; scoped_runtime.mergeValues( diff --git a/test/common/stream_info/utility_test.cc b/test/common/stream_info/utility_test.cc index 21e6f69c6877..60ec543ab3b5 100644 --- a/test/common/stream_info/utility_test.cc +++ b/test/common/stream_info/utility_test.cc @@ -368,16 +368,61 @@ TEST(ProxyStatusFromStreamInfo, TestAll) { TestScopedRuntime scoped_runtime; scoped_runtime.mergeValues( {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_mapping_more_core_response_flags", "false"}}); + for (const auto& [response_flag, proxy_status_error] : + std::vector>{ + {CoreResponseFlag::FailedLocalHealthCheck, ProxyStatusError::DestinationUnavailable}, + {CoreResponseFlag::NoHealthyUpstream, ProxyStatusError::DestinationUnavailable}, + {CoreResponseFlag::UpstreamRequestTimeout, ProxyStatusError::HttpResponseTimeout}, + {CoreResponseFlag::LocalReset, ProxyStatusError::ConnectionTimeout}, + {CoreResponseFlag::UpstreamRemoteReset, ProxyStatusError::ConnectionTerminated}, + {CoreResponseFlag::UpstreamConnectionFailure, ProxyStatusError::ConnectionRefused}, + {CoreResponseFlag::UpstreamConnectionTermination, + ProxyStatusError::ConnectionTerminated}, + {CoreResponseFlag::UpstreamOverflow, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::NoRouteFound, ProxyStatusError::DestinationNotFound}, + {CoreResponseFlag::RateLimited, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::RateLimitServiceError, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::UpstreamRetryLimitExceeded, ProxyStatusError::DestinationUnavailable}, + {CoreResponseFlag::StreamIdleTimeout, ProxyStatusError::HttpResponseTimeout}, + {CoreResponseFlag::InvalidEnvoyRequestHeaders, ProxyStatusError::HttpRequestError}, + {CoreResponseFlag::DownstreamProtocolError, ProxyStatusError::HttpRequestError}, + {CoreResponseFlag::UpstreamMaxStreamDurationReached, + ProxyStatusError::HttpResponseTimeout}, + {CoreResponseFlag::NoFilterConfigFound, ProxyStatusError::ProxyConfigurationError}, + {CoreResponseFlag::UpstreamProtocolError, ProxyStatusError::HttpProtocolError}, + {CoreResponseFlag::NoClusterFound, ProxyStatusError::DestinationUnavailable}, + {CoreResponseFlag::DnsResolutionFailed, ProxyStatusError::DnsError}}) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(response_flag)).WillByDefault(Return(true)); + EXPECT_THAT(ProxyStatusUtils::fromStreamInfo(stream_info), proxy_status_error); + } +} + +TEST(ProxyStatusFromStreamInfo, TestNewAll) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_mapping_more_core_response_flags", "true"}}); for (const auto& [response_flag, proxy_status_error] : std::vector>{ {CoreResponseFlag::FailedLocalHealthCheck, ProxyStatusError::DestinationUnavailable}, {CoreResponseFlag::NoHealthyUpstream, ProxyStatusError::DestinationUnavailable}, {CoreResponseFlag::UpstreamRequestTimeout, ProxyStatusError::HttpResponseTimeout}, + {CoreResponseFlag::DurationTimeout, ProxyStatusError::ConnectionTimeout}, {CoreResponseFlag::LocalReset, ProxyStatusError::ConnectionTimeout}, {CoreResponseFlag::UpstreamRemoteReset, ProxyStatusError::ConnectionTerminated}, {CoreResponseFlag::UpstreamConnectionFailure, ProxyStatusError::ConnectionRefused}, + {CoreResponseFlag::UnauthorizedExternalService, ProxyStatusError::ConnectionRefused}, {CoreResponseFlag::UpstreamConnectionTermination, ProxyStatusError::ConnectionTerminated}, + {CoreResponseFlag::DownstreamConnectionTermination, + ProxyStatusError::ConnectionTerminated}, + {CoreResponseFlag::OverloadManager, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::DropOverLoad, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::FaultInjected, ProxyStatusError::HttpRequestError}, {CoreResponseFlag::UpstreamOverflow, ProxyStatusError::ConnectionLimitReached}, {CoreResponseFlag::NoRouteFound, ProxyStatusError::DestinationNotFound}, {CoreResponseFlag::RateLimited, ProxyStatusError::ConnectionLimitReached},