diff --git a/test/common/router/BUILD b/test/common/router/BUILD index 76dd1399db7a..5207bb84845b 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -14,6 +14,7 @@ envoy_cc_test( name = "config_impl_test", srcs = ["config_impl_test.cc"], deps = [ + ":route_fuzz_proto", "//source/common/config:metadata_lib", "//source/common/config:rds_json_lib", "//source/common/http:header_map_lib", @@ -21,6 +22,7 @@ envoy_cc_test( "//source/common/json:json_loader_lib", "//source/common/router:config_lib", "//source/extensions/filters/http/common:empty_http_filter_config_lib", + "//test/fuzz:utility_lib", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:registry_lib", @@ -83,6 +85,28 @@ envoy_cc_test( ], ) +envoy_proto_library( + name = "route_fuzz_proto", + srcs = ["route_fuzz.proto"], + generate_python = 0, + deps = [ + "//test/fuzz:common_proto", + "@envoy_api//envoy/api/v2:rds_cc_proto", + ], +) + +envoy_cc_fuzz_test( + name = "route_fuzz_test", + srcs = ["route_fuzz_test.cc"], + corpus = "route_corpus", + deps = [ + ":route_fuzz_proto", + "//source/common/router:config_lib", + "//test/fuzz:utility_lib", + "//test/mocks/server:server_mocks", + ], +) + envoy_cc_test( name = "router_ratelimit_test", srcs = ["router_ratelimit_test.cc"], diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index a4585ad8d447..16874a5224e6 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -17,6 +18,8 @@ #include "extensions/filters/http/common/empty_http_filter_config.h" +#include "test/common/router/route_fuzz.pb.h" +#include "test/fuzz/utility.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" @@ -39,6 +42,40 @@ namespace Envoy { namespace Router { namespace { +// Wrap ConfigImpl, the target of tests to allow us to regenerate the route_fuzz_test +// corpus when run with: +// bazel run //test/common/router:config_impl_test +// --test_env="ROUTE_CORPUS_PATH=$PWD/test/common/router/route_corpus" +class TestConfigImpl : public ConfigImpl { +public: + TestConfigImpl(const envoy::api::v2::RouteConfiguration& config, + Server::Configuration::FactoryContext& factory_context, + bool validate_clusters_default) + : ConfigImpl(config, factory_context, validate_clusters_default), config_(config) {} + + RouteConstSharedPtr route(const Http::HeaderMap& headers, uint64_t random_value) const override { + absl::optional corpus_path = + TestEnvironment::getOptionalEnvVar("ROUTE_CORPUS_PATH"); + if (corpus_path) { + static uint32_t n; + test::common::router::RouteTestCase route_test_case; + route_test_case.mutable_config()->MergeFrom(config_); + route_test_case.mutable_headers()->MergeFrom(Fuzz::toHeaders(headers)); + route_test_case.set_random_value(random_value); + const std::string path = fmt::format("{}/config_impl_test_{}", corpus_path.value(), n++); + const std::string corpus = route_test_case.DebugString(); + { + std::ofstream corpus_file(path); + ENVOY_LOG_MISC(debug, "Writing {} to {}", corpus, path); + corpus_file << corpus; + } + } + return ConfigImpl::route(headers, random_value); + } + + const envoy::api::v2::RouteConfiguration config_; +}; + Http::TestHeaderMapImpl genHeaders(const std::string& host, const std::string& path, const std::string& method) { return Http::TestHeaderMapImpl{{":authority", host}, {":path", path}, {":method", method}}; @@ -228,7 +265,7 @@ TEST(RouteMatcherTest, TestRoutes) { NiceMock request_info; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); // Base routing testing. EXPECT_EQ("instant-server", @@ -524,12 +561,12 @@ TEST(RouteMatcherTest, TestRoutesWithInvalidRegex) { NiceMock request_info; EXPECT_THROW_WITH_REGEX( - ConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_route), factory_context, true), + TestConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_route), factory_context, true), EnvoyException, "Invalid regex '/\\(\\+invalid\\)':"); - EXPECT_THROW_WITH_REGEX( - ConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_virtual_cluster), factory_context, true), - EnvoyException, "Invalid regex '\\^/\\(\\+invalid\\)':"); + EXPECT_THROW_WITH_REGEX(TestConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_virtual_cluster), + factory_context, true), + EnvoyException, "Invalid regex '\\^/\\(\\+invalid\\)':"); } // Validates behavior of request_headers_to_add at router, vhost, and route action levels. @@ -619,7 +656,7 @@ TEST(RouteMatcherTest, TestAddRemoveRequestHeaders) { NiceMock factory_context; NiceMock request_info; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); // Request header manipulation testing. { @@ -726,7 +763,7 @@ name: foo envoy::api::v2::RouteConfiguration route_config = parseRouteConfigurationFromV2Yaml(yaml); - ConfigImpl config(route_config, factory_context, true); + TestConfigImpl config(route_config, factory_context, true); // Request header manipulation testing. { @@ -827,7 +864,7 @@ response_headers_to_remove: ["x-global-remove"] NiceMock factory_context; NiceMock request_info; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); // Response header manipulation testing. { @@ -904,7 +941,7 @@ TEST(RouteMatcherTest, Priority) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); EXPECT_EQ(Upstream::ResourcePriority::High, config.route(genHeaders("www.lyft.com", "/foo", "GET"), 0)->routeEntry()->priority()); @@ -933,7 +970,7 @@ TEST(RouteMatcherTest, NoHostRewriteAndAutoRewrite) { )EOF"; NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -957,7 +994,7 @@ TEST(RouteMatcherTest, NoRedirectAndWebSocket) { )EOF"; NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -1023,7 +1060,7 @@ TEST(RouteMatcherTest, HeaderMatchedRouting) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); { EXPECT_EQ("local_service_without_headers", @@ -1117,11 +1154,11 @@ TEST(RouteMatcherTest, InvalidHeaderMatchedRoutingConfig) { NiceMock factory_context; - EXPECT_NO_THROW( - ConfigImpl(parseRouteConfigurationFromV2Yaml(value_with_regex_chars), factory_context, true)); + EXPECT_NO_THROW(TestConfigImpl(parseRouteConfigurationFromV2Yaml(value_with_regex_chars), + factory_context, true)); EXPECT_THROW_WITH_REGEX( - ConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_regex), factory_context, true), + TestConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_regex), factory_context, true), EnvoyException, "Invalid regex"); } @@ -1166,7 +1203,7 @@ TEST(RouteMatcherTest, QueryParamMatchedRouting) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); { Http::TestHeaderMapImpl headers = genHeaders("example.com", "/", "GET"); @@ -1248,11 +1285,11 @@ TEST(RouteMatcherTest, InvalidQueryParamMatchedRoutingConfig) { NiceMock factory_context; - EXPECT_NO_THROW( - ConfigImpl(parseRouteConfigurationFromV2Yaml(value_with_regex_chars), factory_context, true)); + EXPECT_NO_THROW(TestConfigImpl(parseRouteConfigurationFromV2Yaml(value_with_regex_chars), + factory_context, true)); EXPECT_THROW_WITH_REGEX( - ConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_regex), factory_context, true), + TestConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_regex), factory_context, true), EnvoyException, "Invalid regex"); } @@ -1298,7 +1335,8 @@ class RouterMatcherHashPolicyTest : public testing::Test { ConfigImpl& config() { if (config_ == nullptr) { - config_ = std::unique_ptr{new ConfigImpl(route_config_, factory_context_, true)}; + config_ = std::unique_ptr{ + new TestConfigImpl(route_config_, factory_context_, true)}; } return *config_; } @@ -1308,7 +1346,7 @@ class RouterMatcherHashPolicyTest : public testing::Test { HashPolicy::AddCookieCallback add_cookie_nop_; private: - std::unique_ptr config_; + std::unique_ptr config_; }; TEST_F(RouterMatcherHashPolicyTest, HashHeaders) { @@ -1678,7 +1716,7 @@ TEST(RouteMatcherTest, ClusterHeader) { NiceMock factory_context; NiceMock request_info; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); EXPECT_EQ( "some_cluster", @@ -1733,7 +1771,7 @@ TEST(RouteMatcherTest, ContentType) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); { EXPECT_EQ("local_service", @@ -1784,7 +1822,7 @@ TEST(RouteMatcherTest, Runtime) { ON_CALL(factory_context.runtime_loader_, snapshot()).WillByDefault(ReturnRef(snapshot)); - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); EXPECT_CALL(snapshot, featureEnabled("some_key", 50, 10)).WillOnce(Return(true)); EXPECT_EQ("something_else", @@ -1822,7 +1860,7 @@ TEST(RouteMatcherTest, ShadowClusterNotFound) { EXPECT_CALL(factory_context.cluster_manager_, get("some_cluster")) .WillRepeatedly(Return(nullptr)); - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -1847,7 +1885,7 @@ TEST(RouteMatcherTest, ClusterNotFound) { NiceMock factory_context; EXPECT_CALL(factory_context.cluster_manager_, get("www2")).WillRepeatedly(Return(nullptr)); - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -1872,7 +1910,7 @@ TEST(RouteMatcherTest, ClusterNotFoundNotChecking) { NiceMock factory_context; EXPECT_CALL(factory_context.cluster_manager_, get("www2")).WillRepeatedly(Return(nullptr)); - ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, false); + TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, false); } TEST(RouteMatcherTest, ClusterNotFoundNotCheckingViaConfig) { @@ -1897,7 +1935,7 @@ TEST(RouteMatcherTest, ClusterNotFoundNotCheckingViaConfig) { NiceMock factory_context; EXPECT_CALL(factory_context.cluster_manager_, get("www2")).WillRepeatedly(Return(nullptr)); - ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true); } TEST(RouteMatchTest, ClusterNotFoundResponseCode) { @@ -1912,7 +1950,7 @@ TEST(RouteMatchTest, ClusterNotFoundResponseCode) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, false); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, false); Http::TestHeaderMapImpl headers = genHeaders("www.lyft.com", "/", "GET"); @@ -1934,7 +1972,7 @@ TEST(RouteMatchTest, ClusterNotFoundResponseCodeConfig503) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, false); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, false); Http::TestHeaderMapImpl headers = genHeaders("www.lyft.com", "/", "GET"); @@ -1956,7 +1994,7 @@ TEST(RouteMatchTest, ClusterNotFoundResponseCodeConfig404) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, false); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, false); Http::TestHeaderMapImpl headers = genHeaders("www.lyft.com", "/", "GET"); @@ -1999,7 +2037,7 @@ TEST(RouteMatcherTest, Shadow) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); EXPECT_EQ("some_cluster", config.route(genHeaders("www.lyft.com", "/foo", "GET"), 0) ->routeEntry() @@ -2064,7 +2102,7 @@ TEST(RouteMatcherTest, Retry) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); EXPECT_EQ(std::chrono::milliseconds(0), config.route(genHeaders("www.lyft.com", "/foo", "GET"), 0) @@ -2147,7 +2185,7 @@ TEST(RouteMatcherTest, GrpcRetry) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); EXPECT_EQ(std::chrono::milliseconds(0), config.route(genHeaders("www.lyft.com", "/foo", "GET"), 0) @@ -2228,7 +2266,7 @@ TEST(RouteMatcherTest, TestBadDefaultConfig) { )EOF"; NiceMock factory_context; - EXPECT_THROW(ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -2261,7 +2299,7 @@ TEST(RouteMatcherTest, TestDuplicateDomainConfig) { )EOF"; NiceMock factory_context; - EXPECT_THROW(ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -2284,8 +2322,8 @@ TEST(RouteMatcherTest, TestCaseSensitiveDomainConfig) { NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromV2Yaml(config_with_case_sensitive_domains), - factory_context, true), + TestConfigImpl(parseRouteConfigurationFromV2Yaml(config_with_case_sensitive_domains), + factory_context, true), EnvoyException, "Only unique values for domains are permitted. Duplicate entry of domain www.lyft.com"); } @@ -2509,10 +2547,10 @@ name: foo NiceMock factory_context; - ConfigImpl v1_json_config(parseRouteConfigurationFromJson(v1_json), factory_context, true); + TestConfigImpl v1_json_config(parseRouteConfigurationFromJson(v1_json), factory_context, true); testConfig(v1_json_config); - ConfigImpl v2_yaml_config(parseRouteConfigurationFromV2Yaml(v2_yaml), factory_context, true); + TestConfigImpl v2_yaml_config(parseRouteConfigurationFromV2Yaml(v2_yaml), factory_context, true); testConfig(v2_yaml_config, true); } @@ -2545,7 +2583,7 @@ TEST(RouteMatcherTest, ExclusiveRouteEntryOrDirectResponseEntry) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); { Http::TestHeaderMapImpl headers = genRedirectHeaders("www.lyft.com", "/foo", true, true); @@ -2591,7 +2629,7 @@ TEST(RouteMatcherTest, ExclusiveWeightedClustersEntryOrDirectResponseEntry) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); { Http::TestHeaderMapImpl headers = genRedirectHeaders("www.lyft.com", "/foo", true, true); @@ -2673,7 +2711,7 @@ TEST(RouteMatcherTest, WeightedClusters) { NiceMock factory_context; auto& runtime = factory_context.runtime_loader_; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); { Http::TestHeaderMapImpl headers = genRedirectHeaders("www1.lyft.com", "/foo", true, true); @@ -2813,7 +2851,7 @@ TEST(RouteMatcherTest, ExclusiveWeightedClustersOrClusterConfig) { )EOF"; NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -2838,7 +2876,7 @@ TEST(RouteMatcherTest, WeightedClustersMissingClusterList) { )EOF"; NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -2864,7 +2902,7 @@ TEST(RouteMatcherTest, WeightedClustersEmptyClustersList) { )EOF"; NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -2888,8 +2926,8 @@ TEST(RouteMatcherTest, WeightedClustersSumOFWeightsNotEqualToMax) { NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true), EnvoyException, - "Sum of weights in the weighted_cluster should add up to 100"); + TestConfigImpl(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true), + EnvoyException, "Sum of weights in the weighted_cluster should add up to 100"); yaml = R"EOF( virtual_hosts: @@ -2910,8 +2948,8 @@ TEST(RouteMatcherTest, WeightedClustersSumOFWeightsNotEqualToMax) { )EOF"; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true), EnvoyException, - "Sum of weights in the weighted_cluster should add up to 99"); + TestConfigImpl(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true), + EnvoyException, "Sum of weights in the weighted_cluster should add up to 99"); } TEST(RouteMatcherTest, TestWeightedClusterWithMissingWeights) { @@ -2939,7 +2977,7 @@ TEST(RouteMatcherTest, TestWeightedClusterWithMissingWeights) { )EOF"; NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -2975,7 +3013,7 @@ TEST(RouteMatcherTest, TestWeightedClusterInvalidClusterName) { EXPECT_CALL(factory_context.cluster_manager_, get("cluster3-invalid")) .WillRepeatedly(Return(nullptr)); - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -3014,7 +3052,7 @@ TEST(RouteMatcherTest, TestWeightedClusterHeaderManipulation) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); NiceMock request_info; { @@ -3075,7 +3113,7 @@ TEST(BadHttpRouteConfigurationsTest, BadRouteConfig) { NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -3102,7 +3140,7 @@ TEST(BadHttpRouteConfigurationsTest, BadVirtualHostConfig) { NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -3127,7 +3165,7 @@ TEST(BadHttpRouteConfigurationsTest, BadRouteEntryConfig) { NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -3153,7 +3191,7 @@ TEST(BadHttpRouteConfigurationsTest, BadRouteEntryConfigPrefixAndPath) { NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, + TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, "routes must specify one of prefix/path/regex"); } @@ -3179,7 +3217,7 @@ TEST(BadHttpRouteConfigurationsTest, BadRouteEntryConfigPrefixAndRegex) { NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, + TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, "routes must specify one of prefix/path/regex"); } @@ -3205,7 +3243,7 @@ TEST(BadHttpRouteConfigurationsTest, BadRouteEntryConfigPathAndRegex) { NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, + TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, "routes must specify one of prefix/path/regex"); ; } @@ -3233,7 +3271,7 @@ TEST(BadHttpRouteConfigurationsTest, BadRouteEntryConfigPrefixAndPathAndRegex) { NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, + TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, "routes must specify one of prefix/path/regex"); } @@ -3257,7 +3295,7 @@ TEST(BadHttpRouteConfigurationsTest, BadRouteEntryConfigMissingPathSpecifier) { NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, + TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, "routes must specify one of prefix/path/regex"); } @@ -3281,7 +3319,7 @@ TEST(BadHttpRouteConfigurationsTest, BadRouteEntryConfigNoRedirectNoClusters) { NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, + TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, "routes must have redirect or one of cluster/cluster_header/weighted_clusters") } @@ -3308,7 +3346,7 @@ TEST(RouteMatcherTest, TestOpaqueConfig) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); const std::multimap& opaque_config = config.route(genHeaders("api.lyft.com", "/api", "GET"), 0)->routeEntry()->opaqueConfig(); @@ -3339,7 +3377,8 @@ TEST(RoutePropertyTest, excludeVHRateLimits) { Http::TestHeaderMapImpl headers = genHeaders("www.lyft.com", "/foo", "GET"); std::unique_ptr config_ptr; - config_ptr.reset(new ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true)); + config_ptr.reset( + new TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true)); EXPECT_TRUE(config_ptr->route(headers, 0)->routeEntry()->includeVirtualHostRateLimits()); json = R"EOF( @@ -3368,7 +3407,8 @@ TEST(RoutePropertyTest, excludeVHRateLimits) { } )EOF"; - config_ptr.reset(new ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true)); + config_ptr.reset( + new TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true)); EXPECT_FALSE(config_ptr->route(headers, 0)->routeEntry()->includeVirtualHostRateLimits()); json = R"EOF( @@ -3398,7 +3438,8 @@ TEST(RoutePropertyTest, excludeVHRateLimits) { } )EOF"; - config_ptr.reset(new ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true)); + config_ptr.reset( + new TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true)); EXPECT_TRUE(config_ptr->route(headers, 0)->routeEntry()->includeVirtualHostRateLimits()); } @@ -3429,7 +3470,7 @@ TEST(RoutePropertyTest, TestVHostCorsConfig) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); const Router::CorsPolicy* cors_policy = config.route(genHeaders("api.lyft.com", "/api", "GET"), 0) @@ -3473,7 +3514,7 @@ TEST(RoutePropertyTest, TestRouteCorsConfig) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); const Router::CorsPolicy* cors_policy = config.route(genHeaders("api.lyft.com", "/api", "GET"), 0)->routeEntry()->corsPolicy(); @@ -3511,7 +3552,7 @@ TEST(RoutePropertyTest, TestBadCorsConfig) { NiceMock factory_context; - EXPECT_THROW(ConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), + EXPECT_THROW(TestConfigImpl(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException); } @@ -3541,7 +3582,7 @@ TEST(RouterMatcherTest, Decorator) { )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); { Http::TestHeaderMapImpl headers = genHeaders("www.lyft.com", "/foo", "GET"); @@ -3601,7 +3642,7 @@ TEST(CustomRequestHeadersTest, AddNewHeader) { )EOF"; NiceMock factory_context; NiceMock request_info; - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true); Http::TestHeaderMapImpl headers = genHeaders("www.lyft.com", "/new_endpoint/foo", "GET"); const RouteEntry* route = config.route(headers, 0)->routeEntry(); route->finalizeRequestHeaders(headers, request_info, true); @@ -3653,7 +3694,7 @@ TEST(CustomRequestHeadersTest, CustomHeaderWrongFormat) { NiceMock factory_context; NiceMock request_info; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true), + TestConfigImpl config(parseRouteConfigurationFromJson(json), factory_context, true), EnvoyException, "Invalid header configuration. Un-terminated variable expression " "'DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT'"); @@ -3795,7 +3836,7 @@ TEST(RouteEntryMetadataMatchTest, ParsesMetadata) { .set_string_value("r4_value"); NiceMock factory_context; - ConfigImpl config(route_config, factory_context, true); + TestConfigImpl config(route_config, factory_context, true); { Http::TestHeaderMapImpl headers = genRedirectHeaders("www.lyft.com", "/both", true, true); @@ -3896,7 +3937,7 @@ name: foo )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); EXPECT_EQ(nullptr, config.route(genRedirectHeaders("www.foo.com", "/foo", true, true), 0)); @@ -3922,7 +3963,7 @@ name: foo )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); const auto* direct_response = config.route(genHeaders("example.com", "/", "GET"), 0)->directResponseEntry(); @@ -3949,7 +3990,7 @@ name: foo NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl invalid_config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true), + TestConfigImpl invalid_config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true), EnvoyException, "response body size is 4097 bytes; maximum is 4096"); } @@ -3980,7 +4021,7 @@ name: foo )EOF"; NiceMock factory_context; - const ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); + const TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); checkPathMatchCriterion(config.route(genHeaders("www.foo.com", "/regex", "GET"), 0).get(), "/rege[xy]", PathMatchType::Regex); @@ -4027,8 +4068,8 @@ name: AllRedirects )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(RedirectPrefixRewrite), factory_context, - true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(RedirectPrefixRewrite), factory_context, + true); EXPECT_EQ(nullptr, config.route(genRedirectHeaders("www.foo.com", "/foo", true, true), 0)); @@ -4147,7 +4188,8 @@ name: AllRedirects )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(RouteDynPathRedirect), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(RouteDynPathRedirect), factory_context, + true); EXPECT_EQ(nullptr, config.route(genRedirectHeaders("www.foo.com", "/foo", true, true), 0)); @@ -4282,7 +4324,7 @@ name: foo )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context, true); { EXPECT_EQ("local_service_without_headers", @@ -4378,7 +4420,7 @@ name: RegexNoMatch )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(RegexRewrite), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(RegexRewrite), factory_context, true); { // Get our regex route entry @@ -4408,7 +4450,7 @@ name: NoIdleTimeout )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(NoIdleTimeot), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(NoIdleTimeot), factory_context, true); Http::TestHeaderMapImpl headers = genRedirectHeaders("idle.lyft.com", "/regex", true, false); const RouteEntry* route_entry = config.route(headers, 0)->routeEntry(); EXPECT_EQ(absl::nullopt, route_entry->idleTimeout()); @@ -4428,7 +4470,7 @@ name: ZeroIdleTimeout )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(ZeroIdleTimeot), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(ZeroIdleTimeot), factory_context, true); Http::TestHeaderMapImpl headers = genRedirectHeaders("idle.lyft.com", "/regex", true, false); const RouteEntry* route_entry = config.route(headers, 0)->routeEntry(); EXPECT_EQ(0, route_entry->idleTimeout().value().count()); @@ -4448,7 +4490,8 @@ name: ExplicitIdleTimeout )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(ExplicitIdleTimeot), factory_context, true); + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(ExplicitIdleTimeot), factory_context, + true); Http::TestHeaderMapImpl headers = genRedirectHeaders("idle.lyft.com", "/regex", true, false); const RouteEntry* route_entry = config.route(headers, 0)->routeEntry(); EXPECT_EQ(7 * 1000, route_entry->idleTimeout().value().count()); @@ -4497,7 +4540,7 @@ class PerFilterConfigsTest : public testing::Test { void checkEach(const std::string& yaml, uint32_t expected_entry, uint32_t expected_route, uint32_t expected_vhost) { - const ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true); + const TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true); const auto route = config.route(genHeaders("www.foo.com", "/", "GET"), 0); const auto* route_entry = route->routeEntry(); @@ -4518,7 +4561,7 @@ class PerFilterConfigsTest : public testing::Test { } void checkNoPerFilterConfig(const std::string& yaml) { - const ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true); + const TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true); const auto route = config.route(genHeaders("www.foo.com", "/", "GET"), 0); const auto* route_entry = route->routeEntry(); @@ -4551,8 +4594,8 @@ name: foo )EOF"; EXPECT_THROW_WITH_MESSAGE( - ConfigImpl(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true), EnvoyException, - "Didn't find a registered implementation for name: 'unknown.filter'"); + TestConfigImpl(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true), + EnvoyException, "Didn't find a registered implementation for name: 'unknown.filter'"); } // Test that a trivially specified NamedHttpFilterConfigFactory ignores per_filter_config without diff --git a/test/common/router/route_corpus/config_impl_test_0 b/test/common/router/route_corpus/config_impl_test_0 new file mode 100644 index 000000000000..88c02a1d294e --- /dev/null +++ b/test/common/router/route_corpus/config_impl_test_0 @@ -0,0 +1,296 @@ +config { + virtual_hosts { + name: "www2" + domains: "lyft.com" + domains: "www.lyft.com" + domains: "w.lyft.com" + domains: "ww.lyft.com" + domains: "wwww.lyft.com" + routes { + match { + prefix: "/new_endpoint" + } + route { + cluster: "www2" + prefix_rewrite: "/api/new_endpoint" + } + } + routes { + match { + path: "/" + } + route { + cluster: "root_www2" + } + } + routes { + match { + prefix: "/" + } + route { + cluster: "www2" + } + } + } + virtual_hosts { + name: "www2_staging" + domains: "www-staging.lyft.net" + domains: "www-staging-orca.lyft.com" + routes { + match { + prefix: "/" + } + route { + cluster: "www2_staging" + } + } + } + virtual_hosts { + name: "wildcard" + domains: "*.foo.com" + domains: "*-bar.baz.com" + routes { + match { + prefix: "/" + } + route { + cluster: "wildcard" + } + } + } + virtual_hosts { + name: "wildcard2" + domains: "*.baz.com" + routes { + match { + prefix: "/" + } + route { + cluster: "wildcard2" + } + } + } + virtual_hosts { + name: "regex" + domains: "bat.com" + routes { + match { + regex: "/t[io]c" + } + route { + cluster: "clock" + } + } + routes { + match { + regex: "/baa+" + } + route { + cluster: "sheep" + } + } + routes { + match { + regex: ".*/\\d{3}$" + } + route { + cluster: "three_numbers" + prefix_rewrite: "/rewrote" + } + } + routes { + match { + regex: ".*" + } + route { + cluster: "regex_default" + } + } + } + virtual_hosts { + name: "regex2" + domains: "bat2.com" + routes { + match { + regex: "" + } + route { + cluster: "nothingness" + } + } + routes { + match { + regex: ".*" + } + route { + cluster: "regex_default" + } + } + } + virtual_hosts { + name: "default" + domains: "*" + routes { + match { + prefix: "/api/application_data" + } + route { + cluster: "ats" + } + } + routes { + match { + path: "/api/locations" + case_sensitive { + } + } + route { + cluster: "locations" + prefix_rewrite: "/rewrote" + } + } + routes { + match { + prefix: "/api/leads/me" + } + route { + cluster: "ats" + } + } + routes { + match { + prefix: "/host/rewrite/me" + } + route { + cluster: "ats" + host_rewrite: "new_host" + } + } + routes { + match { + prefix: "/oldhost/rewrite/me" + } + route { + cluster: "ats" + host_rewrite: "new_oldhost" + } + } + routes { + match { + path: "/foo" + case_sensitive { + value: true + } + } + route { + cluster: "instant-server" + prefix_rewrite: "/bar" + } + } + routes { + match { + path: "/tar" + case_sensitive { + } + } + route { + cluster: "instant-server" + prefix_rewrite: "/car" + } + } + routes { + match { + prefix: "/newhost/rewrite/me" + case_sensitive { + } + } + route { + cluster: "ats" + host_rewrite: "new_host" + } + } + routes { + match { + path: "/FOOD" + case_sensitive { + } + } + route { + cluster: "ats" + prefix_rewrite: "/cAndy" + } + } + routes { + match { + path: "/ApplEs" + case_sensitive { + value: true + } + } + route { + cluster: "instant-server" + prefix_rewrite: "/oranGES" + } + } + routes { + match { + prefix: "/" + } + route { + cluster: "instant-server" + timeout { + seconds: 30 + } + } + } + virtual_clusters { + pattern: "^/rides$" + name: "ride_request" + method: POST + } + virtual_clusters { + pattern: "^/rides/\\d+$" + name: "update_ride" + method: PUT + } + virtual_clusters { + pattern: "^/users/\\d+/chargeaccounts$" + name: "cc_add" + method: POST + } + virtual_clusters { + pattern: "^/users/\\d+/chargeaccounts/(?!validate)\\w+$" + name: "cc_add" + method: PUT + } + virtual_clusters { + pattern: "^/users$" + name: "create_user_login" + method: POST + } + virtual_clusters { + pattern: "^/users/\\d+$" + name: "update_user" + method: PUT + } + virtual_clusters { + pattern: "^/users/\\d+/location$" + name: "ulu" + method: POST + } + } +} +headers { + headers { + key: ":authority" + value: "api.lyft.com" + } + headers { + key: ":path" + value: "/" + } + headers { + key: ":method" + value: "GET" + } +} diff --git a/test/common/router/route_corpus/empty b/test/common/router/route_corpus/empty new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/common/router/route_fuzz.proto b/test/common/router/route_fuzz.proto new file mode 100644 index 000000000000..c9d362c5d0a0 --- /dev/null +++ b/test/common/router/route_fuzz.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package test.common.router; + +import "envoy/api/v2/rds.proto"; +import "test/fuzz/common.proto"; + +// Structured input for route_fuzz_test. + +message RouteTestCase { + envoy.api.v2.RouteConfiguration config = 1; + test.fuzz.Headers headers = 2; + uint32 random_value = 3; +} diff --git a/test/common/router/route_fuzz_test.cc b/test/common/router/route_fuzz_test.cc new file mode 100644 index 000000000000..72b806de6347 --- /dev/null +++ b/test/common/router/route_fuzz_test.cc @@ -0,0 +1,43 @@ +#include "envoy/api/v2/rds.pb.validate.h" + +#include "common/router/config_impl.h" + +#include "test/common/router/route_fuzz.pb.h" +#include "test/fuzz/fuzz_runner.h" +#include "test/fuzz/utility.h" +#include "test/mocks/server/mocks.h" + +namespace Envoy { +namespace Router { + +// TODO(htuch): figure out how to generate via a genrule from config_impl_test the full corpus. +DEFINE_PROTO_FUZZER(const test::common::router::RouteTestCase& input) { + try { + NiceMock request_info; + NiceMock factory_context; + MessageUtil::validate(input.config()); + ConfigImpl config(input.config(), factory_context, true); + Http::TestHeaderMapImpl headers = Fuzz::fromHeaders(input.headers()); + // It's a precondition of routing that {host, path:, x-fowarded-proto} headers exists, HCM + // enforces this. + if (!headers.has("host")) { + headers.addCopy("host", "example.com"); + } + if (!headers.has(":path")) { + headers.addCopy(":path", "/"); + } + if (!headers.has("x-forwarded-proto")) { + headers.addCopy("x-forwarded-proto", "http"); + } + auto route = config.route(headers, input.random_value()); + if (route != nullptr && route->routeEntry() != nullptr) { + route->routeEntry()->finalizeRequestHeaders(headers, request_info, true); + } + ENVOY_LOG_MISC(trace, "Success"); + } catch (const EnvoyException& e) { + ENVOY_LOG_MISC(debug, "EnvoyException: {}", e.what()); + } +} + +} // namespace Router +} // namespace Envoy diff --git a/test/fuzz/utility.h b/test/fuzz/utility.h index 40b49f979698..b9229fd20ff1 100644 --- a/test/fuzz/utility.h +++ b/test/fuzz/utility.h @@ -19,6 +19,20 @@ inline Http::TestHeaderMapImpl fromHeaders(const test::fuzz::Headers& headers) { return header_map; } +// Convert from HeaderMap to test proto Headers. +inline test::fuzz::Headers toHeaders(const Http::HeaderMapImpl headers) { + test::fuzz::Headers fuzz_headers; + headers.iterate( + [](const Http::HeaderEntry& header, void* ctxt) -> Http::HeaderMap::Iterate { + auto* fuzz_header = static_cast(ctxt)->add_headers(); + fuzz_header->set_key(header.key().c_str()); + fuzz_header->set_value(header.value().c_str()); + return Http::HeaderMap::Iterate::Continue; + }, + &fuzz_headers); + return fuzz_headers; +} + inline TestRequestInfo fromRequestInfo(const test::fuzz::RequestInfo& request_info) { TestRequestInfo test_request_info; test_request_info.metadata_ = request_info.dynamic_metadata();