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

ext_authz: do the authentication even the direct response is set #17546

Merged
merged 10 commits into from
Aug 10, 2021
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Bug Fixes
* access log: fix ``%UPSTREAM_CLUSTER%`` when used in http upstream access logs. Previously, it was always logging as an unset value.
* aws request signer: fix the AWS Request Signer extension to correctly normalize the path and query string to be signed according to AWS' guidelines, so that the hash on the server side matches. See `AWS SigV4 documentaion <https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html>`_.
* cluster: delete pools when they're idle to fix unbounded memory use when using PROXY protocol upstream with tcp_proxy. This behavior can be temporarily reverted by setting the ``envoy.reloadable_features.conn_pool_delete_when_idle`` runtime guard to false.
* ext_authz: fixed skipping authentication when returning either a direct response or a redirect. This behavior can be temporarily reverted by setting the ``envoy.reloadable_features.http_ext_authz_do_not_skip_direct_response_and_redirect`` runtime guard to false.
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 this should also be mentioned in "incompatible behavior changes". This behavior has been present for at least a few years, and I know some people are relying on the old behavior.

Copy link
Member Author

Choose a reason for hiding this comment

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

that make sense, I can move it.

* hcm: remove deprecation for :ref:`xff_num_trusted_hops <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.xff_num_trusted_hops>` and forbid mixing ip detection extensions with old related knobs.
* listener: fixed an issue on Windows where connections are not handled by all worker threads.
* xray: fix the AWS X-Ray tracer bug where span's error, fault and throttle information was not reported properly as per the `AWS X-Ray documentation <https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html>`_. Before this fix, server error was reported under 'annotations' section of the segment data.
Expand Down
12 changes: 3 additions & 9 deletions source/common/router/config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1012,15 +1012,9 @@ void RouteEntryImplBase::validateClusters(
void RouteEntryImplBase::traversePerFilterConfig(
const std::string& filter_name,
std::function<void(const Router::RouteSpecificFilterConfig&)> cb) const {
const Router::RouteEntry* route_entry = routeEntry();

// TODO(soulxu): This has similar bug with https://github.com/envoyproxy/envoy/issues/17377
// it should be fixed.
if (route_entry != nullptr) {
auto maybe_vhost_config = vhost_.perFilterConfig(filter_name);
if (maybe_vhost_config != nullptr) {
cb(*maybe_vhost_config);
}
auto maybe_vhost_config = vhost_.perFilterConfig(filter_name);
if (maybe_vhost_config != nullptr) {
cb(*maybe_vhost_config);
}

auto maybe_route_config = per_filter_configs_.get(filter_name);
Expand Down
1 change: 1 addition & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ constexpr const char* runtime_features[] = {
"envoy.reloadable_features.health_check.immediate_failure_exclude_from_cluster",
"envoy.reloadable_features.http2_consume_stream_refused_errors",
"envoy.reloadable_features.http2_skip_encoding_empty_trailers",
"envoy.reloadable_features.http_ext_authz_do_not_skip_direct_response_and_redirect",
"envoy.reloadable_features.http_transport_failure_reason_in_body",
"envoy.reloadable_features.improved_stream_limit_handling",
"envoy.reloadable_features.internal_redirects_with_body",
Expand Down
5 changes: 4 additions & 1 deletion source/extensions/filters/http/ext_authz/ext_authz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,10 @@ void Filter::continueDecoding() {
}

Filter::PerRouteFlags Filter::getPerRouteFlags(const Router::RouteConstSharedPtr& route) const {
if (route == nullptr || route->routeEntry() == nullptr) {
if (route == nullptr ||
(!Runtime::runtimeFeatureEnabled(
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't this affect redirects too? Not sure if these be passed to ext_authz?

Copy link
Member Author

@soulxu soulxu Aug 5, 2021

Choose a reason for hiding this comment

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

thanks for the good point, I tested the redirect case, this change affect it.

@mattklein123 WDYT?

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's correct to fix redirect also. You might want to rename the runtime flag and update the release notes?

/wait

Copy link
Member Author

Choose a reason for hiding this comment

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

right, let me update

"envoy.reloadable_features.http_ext_authz_do_not_skip_direct_response_and_redirect") &&
route->routeEntry() == nullptr)) {
return PerRouteFlags{true /*skip_check_*/, false /*skip_request_body_buffering_*/};
}

Expand Down
26 changes: 26 additions & 0 deletions test/common/router/config_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7988,6 +7988,32 @@ TEST_F(PerFilterConfigsTest, RouteLocalTypedConfig) {
checkEach(yaml, 123, expected_traveled_config);
}

TEST_F(PerFilterConfigsTest, RouteLocalTypedConfigWithDirectResponse) {
const std::string yaml = R"EOF(
virtual_hosts:
- name: bar
domains: ["*"]
routes:
- match: { prefix: "/" }
direct_response:
status: 200
typed_per_filter_config:
test.filter:
"@type": type.googleapis.com/google.protobuf.Timestamp
value:
seconds: 123
typed_per_filter_config:
test.filter:
"@type": type.googleapis.com/google.protobuf.Struct
value:
seconds: 456
)EOF";

factory_context_.cluster_manager_.initializeClusters({"baz"}, {});
absl::InlinedVector<uint32_t, 3> expected_traveled_config({456, 123});
checkEach(yaml, 123, expected_traveled_config);
}

TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(WeightedClusterConfig)) {
TestDeprecatedV2Api _deprecated_v2_api;
const std::string yaml = R"EOF(
Expand Down
1 change: 1 addition & 0 deletions test/extensions/filters/http/ext_authz/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ envoy_extension_cc_test(
"//test/mocks/runtime:runtime_mocks",
"//test/mocks/tracing:tracing_mocks",
"//test/mocks/upstream:cluster_manager_mocks",
"//test/test_common:test_runtime_lib",
"//test/test_common:utility_lib",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
"@envoy_api//envoy/extensions/filters/http/ext_authz/v3:pkg_cc_proto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,47 @@ TEST_P(ExtAuthzHttpIntegrationTest, DefaultCaseSensitiveStringMatcher) {
ASSERT_TRUE(header_entry.empty());
}

TEST_P(ExtAuthzHttpIntegrationTest, DirectReponse) {
config_helper_.addConfigModifier(
[](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
hcm) {
auto* virtual_hosts = hcm.mutable_route_config()->mutable_virtual_hosts(0);
virtual_hosts->mutable_routes(0)->clear_route();
envoy::config::route::v3::Route* route = virtual_hosts->mutable_routes(0);
route->mutable_direct_response()->set_status(204);
});

initializeConfig();
HttpIntegrationTest::initialize();
initiateClientConnection();
waitForExtAuthzRequest();

ASSERT_TRUE(response_->waitForEndStream());
EXPECT_TRUE(response_->complete());
EXPECT_EQ("204", response_->headers().Status()->value().getStringView());
}

TEST_P(ExtAuthzHttpIntegrationTest, RedirectResponse) {
config_helper_.addConfigModifier(
[](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
hcm) {
auto* virtual_hosts = hcm.mutable_route_config()->mutable_virtual_hosts(0);
virtual_hosts->mutable_routes(0)->clear_route();
envoy::config::route::v3::Route* route = virtual_hosts->mutable_routes(0);
route->mutable_redirect()->set_path_redirect("/redirect");
});

initializeConfig();
HttpIntegrationTest::initialize();
initiateClientConnection();
waitForExtAuthzRequest();

ASSERT_TRUE(response_->waitForEndStream());
EXPECT_TRUE(response_->complete());
EXPECT_EQ("301", response_->headers().Status()->value().getStringView());
EXPECT_EQ("http://host/redirect", response_->headers().getLocationValue());
}

class ExtAuthzLocalReplyIntegrationTest : public HttpIntegrationTest,
public TestWithParam<Network::Address::IpVersion> {
public:
Expand Down
23 changes: 20 additions & 3 deletions test/extensions/filters/http/ext_authz/ext_authz_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "test/mocks/tracing/mocks.h"
#include "test/mocks/upstream/cluster_manager.h"
#include "test/test_common/printers.h"
#include "test/test_common/test_runtime.h"
#include "test/test_common/utility.h"

#include "gmock/gmock.h"
Expand Down Expand Up @@ -1603,14 +1604,30 @@ TEST_P(HttpFilterTestParam, DisabledOnRouteWithRequestBody) {
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_));
}

// Test that the request continues when the filter_callbacks has no route.
// Test that authentication will do when the filter_callbacks has no route.(both
// direct response and redirect have no route)
TEST_P(HttpFilterTestParam, NoRoute) {
EXPECT_CALL(*filter_callbacks_.route_, routeEntry()).WillOnce(Return(nullptr));
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false));
EXPECT_CALL(*filter_callbacks_.route_, routeEntry()).WillRepeatedly(Return(nullptr));
prepareCheck();
EXPECT_CALL(*client_, check(_, _, _, _));
EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark,
filter_->decodeHeaders(request_headers_, false));
EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false));
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_));
}

// Test that the authentication will be skipped when the filter_callbacks has no route(both
// direct response and redirect have no route) when the runtime flag
// `envoy.reloadable_features.http_ext_authz_do_not_skip_direct_response_and_redirect` is false.
TEST_P(HttpFilterTestParam, NoRouteWithSkipAuth) {
TestScopedRuntime scoped_runtime;
Runtime::LoaderSingleton::getExisting()->mergeValues(
{{"envoy.reloadable_features.http_ext_authz_do_not_skip_direct_response_and_redirect",
"false"}});
EXPECT_CALL(*filter_callbacks_.route_, routeEntry()).WillOnce(Return(nullptr));
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false));
}

// Test that the request is stopped till there is an OK response back after which it continues on.
TEST_P(HttpFilterTestParam, OkResponse) {
InSequence s;
Expand Down