From 2917f74c28534634c021f3d86552891d45daa827 Mon Sep 17 00:00:00 2001 From: "Antonio V. Leonti" <53806445+antoniovleonti@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:49:47 -0400 Subject: [PATCH 01/31] fix jwt_authn fuzzer crash on invalid provider URI (#34567) * fix jwt_authz fuzzer crash on invalid provider URI Signed-off-by: antoniovleonti * move validation to filter_config.cc Signed-off-by: antoniovleonti * fix tests Signed-off-by: antoniovleonti * add runtime guard Signed-off-by: antoniovleonti * remote erroneous runtime guard Signed-off-by: antoniovleonti * fix runtime feature name & add test Signed-off-by: antoniovleonti --------- Signed-off-by: antoniovleonti Signed-off-by: Antonio V. Leonti <53806445+antoniovleonti@users.noreply.github.com> --- .../filters/http/jwt_authn/v3/config.proto | 2 +- changelogs/current.yaml | 5 ++ source/common/runtime/runtime_features.cc | 1 + .../filters/http/jwt_authn/filter_config.cc | 19 ++++++ test/extensions/filters/http/jwt_authn/BUILD | 1 + .../http/jwt_authn/authenticator_test.cc | 2 +- .../http/jwt_authn/filter_config_test.cc | 60 +++++++++++++++++++ .../jwt_authn/jwt_authn_corpus/invalid_uri | 53 ++++++++++++++++ .../http/jwt_authn/provider_verifier_test.cc | 2 +- .../filters/http/jwt_authn/test_common.h | 30 +++++----- 10 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 test/extensions/filters/http/jwt_authn/jwt_authn_corpus/invalid_uri diff --git a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 5bb6be92294f..18cd08c5c9cb 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -395,7 +395,7 @@ message RemoteJwks { // cluster: jwt.www.googleapis.com|443 // timeout: 1s // - config.core.v3.HttpUri http_uri = 1; + config.core.v3.HttpUri http_uri = 1 [(validate.rules).message = {required: true}]; // Duration after which the cached JWKS should be expired. If not specified, default cache // duration is 10 minutes. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 63a0bc130a30..b71129b626f9 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -108,6 +108,11 @@ minor_behavior_changes: - area: filters change: | Set ``WWW-Authenticate`` header for 401 responses from the Basic Auth filter. +- area: jwt_authn + change: | + jwt_authn now validates provider URIs. If the validation is too strict it can temporarily be + disabled by setting the runtime guard ``envoy.reloadable_features.jwt_authn_validate_uri`` to + false. - area: http change: | Removed runtime guard ``envoy.reloadable_features.refresh_rtt_after_request`` and legacy code path. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index bf096e110539..867cfd642b3b 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -64,6 +64,7 @@ RUNTIME_GUARD(envoy_reloadable_features_http_filter_avoid_reentrant_local_reply) RUNTIME_GUARD(envoy_reloadable_features_http_reject_path_with_fragment); RUNTIME_GUARD(envoy_reloadable_features_http_route_connect_proxy_by_default); RUNTIME_GUARD(envoy_reloadable_features_immediate_response_use_filter_mutation_rule); +RUNTIME_GUARD(envoy_reloadable_features_jwt_authn_validate_uri); RUNTIME_GUARD(envoy_reloadable_features_no_downgrade_to_canonical_name); RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name); RUNTIME_GUARD(envoy_reloadable_features_normalize_host_for_preresolve_dfp_dns); diff --git a/source/extensions/filters/http/jwt_authn/filter_config.cc b/source/extensions/filters/http/jwt_authn/filter_config.cc index b6d83d17ae90..71565c4486a9 100644 --- a/source/extensions/filters/http/jwt_authn/filter_config.cc +++ b/source/extensions/filters/http/jwt_authn/filter_config.cc @@ -3,6 +3,8 @@ #include // std::sort #include "source/common/common/empty_string.h" +#include "source/common/http/utility.h" +#include "source/common/runtime/runtime_features.h" using envoy::extensions::filters::http::jwt_authn::v3::RequirementRule; @@ -22,6 +24,23 @@ FilterConfigImpl::FilterConfigImpl( jwks_cache_ = JwksCache::create(proto_config_, context, Common::JwksFetcher::create, stats_); + // Validate provider URIs. + // Note that the PGV well-known regex for URI is not implemented in C++, otherwise we could add a + // PGV rule instead of doing this check manually. + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.jwt_authn_validate_uri")) { + for (const auto& provider_pair : proto_config_.providers()) { + const auto provider_value = std::get<1>(provider_pair); + if (provider_value.has_remote_jwks()) { + absl::string_view provider_uri = provider_value.remote_jwks().http_uri().uri(); + Http::Utility::Url url; + if (!url.initialize(provider_uri, /*is_connect=*/false)) { + throw EnvoyException(fmt::format("Provider '{}' has an invalid URI: '{}'", + std::get<0>(provider_pair), provider_uri)); + } + } + } + } + std::vector names; for (const auto& it : proto_config_.requirement_map()) { names.push_back(it.first); diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index b6eaeb6fb312..24e5e3faac22 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -66,6 +66,7 @@ envoy_extension_cc_test( "//test/extensions/filters/http/jwt_authn:test_common_lib", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:instance_mocks", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/filters/http/jwt_authn/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/http/jwt_authn/authenticator_test.cc b/test/extensions/filters/http/jwt_authn/authenticator_test.cc index 40a2b1117efc..0202d588358b 100644 --- a/test/extensions/filters/http/jwt_authn/authenticator_test.cc +++ b/test/extensions/filters/http/jwt_authn/authenticator_test.cc @@ -1021,7 +1021,7 @@ TEST_F(AuthenticatorTest, TestAllowFailedMultipleIssuers) { provider.set_issuer("https://other.com"); provider.add_audiences("other_service"); auto& uri = *provider.mutable_remote_jwks()->mutable_http_uri(); - uri.set_uri("https://pubkey_server/pubkey_path"); + uri.set_uri("https://www.pubkey-server.com/pubkey-path"); uri.set_cluster("pubkey_cluster"); auto header = provider.add_from_headers(); header->set_name("expired-auth"); diff --git a/test/extensions/filters/http/jwt_authn/filter_config_test.cc b/test/extensions/filters/http/jwt_authn/filter_config_test.cc index c5668a038c19..0d9e703c49d6 100644 --- a/test/extensions/filters/http/jwt_authn/filter_config_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_config_test.cc @@ -7,6 +7,7 @@ #include "test/extensions/filters/http/jwt_authn/test_common.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/instance.h" +#include "test/test_common/test_runtime.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -308,6 +309,8 @@ TEST(HttpJwtAuthnFilterConfigTest, RemoteJwksDurationVeryBig) { remote_jwks: cache_duration: seconds: 5223372036 + http_uri: + uri: http://www.google.com )"; JwtAuthentication proto_config; @@ -318,6 +321,63 @@ TEST(HttpJwtAuthnFilterConfigTest, RemoteJwksDurationVeryBig) { HasSubstr("Duration out-of-range")); } +TEST(HttpJwtAuthnFilterConfigTest, RemoteJwksInvalidUri) { + // Invalid URI should fail config validation. + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + remote_jwks: + http_uri: + uri: http://www.not\nvalid.com +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + EXPECT_THAT_THROWS_MESSAGE(FilterConfigImpl(proto_config, "", context), EnvoyException, + HasSubstr("invalid URI")); +} + +TEST(HttpJwtAuthnFilterConfigTest, RemoteJwksInvalidUriValidationDisabled) { + // Disabling this runtime feature should allow invalid URIs. + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.jwt_authn_validate_uri", "false"}}); + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + remote_jwks: + http_uri: + uri: http://www.not\nvalid.com +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + EXPECT_NO_THROW(FilterConfigImpl(proto_config, "", context)); +} + +TEST(HttpJwtAuthnFilterConfigTest, RemoteJwksValidUri) { + // Valid URI should not fail config validation. + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + remote_jwks: + http_uri: + uri: http://www.valid.com/resource +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + FilterConfigImpl(proto_config, "", context); +} + TEST(HttpJwtAuthnFilterConfigTest, RemoteJwksAsyncFetchRefetchDurationVeryBig) { // failed_refetch_duration.duration.seconds should be less than: // 9223372036 = max_int64 / 1e9, which is about 300 years. diff --git a/test/extensions/filters/http/jwt_authn/jwt_authn_corpus/invalid_uri b/test/extensions/filters/http/jwt_authn/jwt_authn_corpus/invalid_uri new file mode 100644 index 000000000000..cb08c4ebeca7 --- /dev/null +++ b/test/extensions/filters/http/jwt_authn/jwt_authn_corpus/invalid_uri @@ -0,0 +1,53 @@ +config { + providers { + key: "provider2" + value { + issuer: "https://example.com" + remote_jwks { + http_uri { + uri: "\001\000\000\000" + cluster: "pubkey_cluster" + timeout { + seconds: 5 + nanos: 4 + } + } + } + forward: true + forward_payload_header: "provider2" + header_in_metadata: "use" + } + } + rules { + match { + prefix: "/" + } + requires { + requires_any { + requirements { + requires_any { + requirements { + provider_name: "provider2" + } + requirements { + } + } + } + requirements { + } + } + } + } +} +request_data { + headers { + headers { + key: "authorization" + value: "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImF1ZCI6ImV4YW1wbGVfc2VydmljZSIsImV4cCI6MjAwMTAwMTAwMX0.n45uWZfIBZwCIPiL0K8Ca3tmm-ZlsDrC79_vXCspPwk5oxdSn983tuC9GfVWKXWUMHe11DsB02b19Ow-fmoEzooTFn65Ml7G34nW07amyM6lETiMhNzyiunctplOr6xKKJHmzTUhfTirvDeG-q9n24-8lH7GP8GgHvDlgSM9OY7TGp81bRcnZBmxim_UzHoYO3_c8OP4ZX3xG5PfihVk5G0g6wcHrO70w0_64JgkKRCrLHMJSrhIgp9NHel_CNOnL0AjQKe9IGblJrMuouqYYS0zEWwmOVUWUSxQkoLpldQUVefcfjQeGjz8IlvktRa77FYexfP590ACPyXrivtsxg" + } + } +} +remote_jwks: "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"62a93512c9ee4c7f8067b5a216dade2763d32a47\",\"n\":\"0YWnm_eplO9BFtXszMRQNL5UtZ8HJdTH2jK7vjs4XdLkPW7YBkkm_2xNgcaVpkW0VT2l4mU3KftR-6s3Oa5Rnz5BrWEUkCTVVolR7VYksfqIB2I_x5yZHdOiomMTcm3DheUUCgbJRv5OKRnNqszA4xHn3tA3Ry8VO3X7BgKZYAUh9fyZTFLlkeAh0-bLK5zvqCmKW5QgDIXSxUTJxPjZCgfx1vmAfGqaJb-nvmrORXQ6L284c73DUL7mnt6wj3H6tVqPKA27j56N0TB1Hfx4ja6Slr8S4EB3F1luYhATa1PKUSH8mYDW11HolzZmTQpRoLV8ZoHbHEaTfqX_aYahIw\",\"e\":\"AQAB\"},{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"b3319a147514df7ee5e4bcdee51350cc890cc89e\",\"n\":\"qDi7Tx4DhNvPQsl1ofxxc2ePQFcs-L0mXYo6TGS64CY_2WmOtvYlcLNZjhuddZVV2X88m0MfwaSA16wE-RiKM9hqo5EY8BPXj57CMiYAyiHuQPp1yayjMgoE1P2jvp4eqF-BTillGJt5W5RuXti9uqfMtCQdagB8EC3MNRuU_KdeLgBy3lS3oo4LOYd-74kRBVZbk2wnmmb7IhP9OoLc1-7-9qU1uhpDxmE6JwBau0mDSwMnYDS4G_ML17dC-ZDtLd1i24STUw39KH0pcSdfFbL2NtEZdNeam1DDdk0iUtJSPZliUHJBI_pj8\001\000\000\002M-2Mn_oA8jBuI8YKwBqYkZCN1I95Q\",\"e\":\"AQAB\"}]}" +num_calls: 3 +filter_state_selector: "example_service22" +filter_on_destroy: true diff --git a/test/extensions/filters/http/jwt_authn/provider_verifier_test.cc b/test/extensions/filters/http/jwt_authn/provider_verifier_test.cc index fa17239d1ce4..ad6da9037b0b 100644 --- a/test/extensions/filters/http/jwt_authn/provider_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/provider_verifier_test.cc @@ -168,7 +168,7 @@ TEST_F(ProviderVerifierTest, TestTokenRequirementProviderMismatch) { - https://example_service2/ remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://pubkey-server/pubkey-path cluster: pubkey_cluster forward_payload_header: example-auth-userinfo claim_to_headers: diff --git a/test/extensions/filters/http/jwt_authn/test_common.h b/test/extensions/filters/http/jwt_authn/test_common.h index 813565e26bc6..6ebe04c20350 100644 --- a/test/extensions/filters/http/jwt_authn/test_common.h +++ b/test/extensions/filters/http/jwt_authn/test_common.h @@ -75,7 +75,7 @@ const char SubjectConfig[] = R"( suffix: "@example.com" remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -85,7 +85,7 @@ const char SubjectConfig[] = R"( prefix: spiffe://spiffe.example.com/ remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -93,7 +93,7 @@ const char SubjectConfig[] = R"( issuer: https://nosub.com remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -105,7 +105,7 @@ const char SubjectConfig[] = R"( regex: "spiffe://.*\\.example\\.com/.*" remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -121,7 +121,7 @@ const char ExpirationConfig[] = R"( seconds: 86400 remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -130,7 +130,7 @@ const char ExpirationConfig[] = R"( require_expiration: true remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -139,7 +139,7 @@ const char ExpirationConfig[] = R"( require_expiration: false remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -156,7 +156,7 @@ const char ExampleConfig[] = R"( - https://example_service2/ remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -197,7 +197,7 @@ const char ClaimToHeadersConfig[] = R"( - https://example_service2/ remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -226,7 +226,7 @@ const char PayloadClearRouteCacheConfig[] = R"( - https://example_service2/ remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -252,7 +252,7 @@ const char ExampleConfigWithRegEx[] = R"( - https://example_service2/ remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster timeout: seconds: 5 @@ -502,7 +502,7 @@ const char RequiresAllConfig[] = R"( - https://example_service2/ remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster from_params: ["jwt_a"] forward_payload_header: example-auth-userinfo @@ -512,7 +512,7 @@ const char RequiresAllConfig[] = R"( - other_service remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster from_params: ["jwt_b"] forward_payload_header: other-auth-userinfo @@ -536,7 +536,7 @@ const char RequiresAnyConfig[] = R"( - https://example_service2/ remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster from_headers: - name: a @@ -550,7 +550,7 @@ const char RequiresAnyConfig[] = R"( - other_service remote_jwks: http_uri: - uri: https://pubkey_server/pubkey_path + uri: https://www.pubkey-server.com/pubkey-path cluster: pubkey_cluster from_headers: - name: a From 70d87972ff5307f9edcdc4a2a9d3ed533bd2dc10 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 27 Jun 2024 11:03:26 -0400 Subject: [PATCH 02/31] ssl socket: allow subclasses (#34938) Signed-off-by: Alyssa Wilk --- source/common/tls/client_ssl_socket.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/common/tls/client_ssl_socket.h b/source/common/tls/client_ssl_socket.h index b7f2c1dfd8c7..7a776a622890 100644 --- a/source/common/tls/client_ssl_socket.h +++ b/source/common/tls/client_ssl_socket.h @@ -56,11 +56,12 @@ class ClientSslSocketFactory : public Network::CommonUpstreamTransportSocketFact Envoy::Ssl::ClientContextSharedPtr sslCtx() override; -private: +protected: ClientSslSocketFactory(Envoy::Ssl::ClientContextConfigPtr config, Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope, absl::Status& creation_status); +private: Envoy::Ssl::ContextManager& manager_; Stats::Scope& stats_scope_; SslSocketFactoryStats stats_; From d7c70186317ec2a8cf4eec366b66e8c157cb112d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20B=C3=A9ky?= Date: Thu, 27 Jun 2024 11:04:28 -0400 Subject: [PATCH 03/31] [balsa] Reject CR without LF in chunk extension. (#34906) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bence Béky --- source/common/http/http1/balsa_parser.cc | 1 + test/common/http/http1/codec_impl_test.cc | 27 +++++------------------ 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/source/common/http/http1/balsa_parser.cc b/source/common/http/http1/balsa_parser.cc index fb6109a4a2c6..000d14c61794 100644 --- a/source/common/http/http1/balsa_parser.cc +++ b/source/common/http/http1/balsa_parser.cc @@ -161,6 +161,7 @@ BalsaParser::BalsaParser(MessageType type, ParserCallbacks* connection, size_t m http_validation_policy.require_content_length_if_body_required = false; http_validation_policy.disallow_invalid_header_characters_in_response = true; http_validation_policy.disallow_lone_cr_in_request_headers = true; + http_validation_policy.disallow_lone_cr_in_chunk_extension = true; framer_.set_http_validation_policy(http_validation_policy); framer_.set_balsa_headers(&headers_); diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 262220476639..284c32436167 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -5271,14 +5271,8 @@ TEST_P(Http1ServerConnectionImplTest, ChunkExtensionInvalidCR) { {"transfer-encoding", "chunked"}, }; EXPECT_CALL(decoder, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - - if (parser_impl_ == Http1ParserImpl::BalsaParser) { - EXPECT_CALL(decoder, decodeData(BufferStringEqual("Hello World"), false)); - EXPECT_CALL(decoder, decodeData(BufferStringEqual(""), true)); - } else { - EXPECT_CALL(decoder, - sendLocalReply(Http::Code::BadRequest, "Bad Request", _, _, "http1.codec_error")); - } + EXPECT_CALL(decoder, + sendLocalReply(Http::Code::BadRequest, "Bad Request", _, _, "http1.codec_error")); // SPELLCHECKER(off) Buffer::OwnedImpl buffer("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n" @@ -5287,11 +5281,10 @@ TEST_P(Http1ServerConnectionImplTest, ChunkExtensionInvalidCR) { "0\r\n\r\n"); // SPELLCHECKER(on) auto status = codec_->dispatch(buffer); + EXPECT_TRUE(isCodecProtocolError(status)); if (parser_impl_ == Http1ParserImpl::BalsaParser) { - EXPECT_TRUE(status.ok()); - EXPECT_EQ(0u, buffer.length()); + EXPECT_EQ(status.message(), "http/1.1 protocol error: INVALID_CHUNK_EXTENSION"); } else { - EXPECT_TRUE(isCodecProtocolError(status)); #ifdef ENVOY_ENABLE_UHV EXPECT_EQ(status.message(), "http/1.1 protocol error: HPE_INVALID_CHUNK_SIZE"); #else @@ -5316,13 +5309,6 @@ TEST_P(Http1ClientConnectionImplTest, ChunkExtensionInvalidCR) { }; EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); - TestResponseHeaderMapImpl expected_headers{{":status", "200"}, {"transfer-encoding", "chunked"}}; - if (parser_impl_ == Http1ParserImpl::BalsaParser) { - EXPECT_CALL(response_decoder, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual("Hello World"), false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual(""), true)); - } - // SPELLCHECKER(off) Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n" "6;\ra\r\nHello \r\n" @@ -5330,11 +5316,10 @@ TEST_P(Http1ClientConnectionImplTest, ChunkExtensionInvalidCR) { "0\r\n\r\n"); // SPELLCHECKER(on) auto status = codec_->dispatch(buffer); + EXPECT_TRUE(isCodecProtocolError(status)); if (parser_impl_ == Http1ParserImpl::BalsaParser) { - EXPECT_TRUE(status.ok()); - EXPECT_EQ(0u, buffer.length()); + EXPECT_EQ(status.message(), "http/1.1 protocol error: INVALID_CHUNK_EXTENSION"); } else { - EXPECT_TRUE(isCodecProtocolError(status)); #ifdef ENVOY_ENABLE_UHV EXPECT_EQ(status.message(), "http/1.1 protocol error: HPE_INVALID_CHUNK_SIZE"); #else From 1a98fd5d982fb1cda9ae9ade5d5aee1a54dcbb49 Mon Sep 17 00:00:00 2001 From: Tianyu <72890320+tyxia@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:09:09 -0400 Subject: [PATCH 04/31] ext_proc: observability mode with deferred stream closure (part 2) (#34491) --------- Signed-off-by: tyxia --- changelogs/current.yaml | 5 + .../extensions/filters/http/ext_proc/client.h | 1 + .../filters/http/ext_proc/client_impl.cc | 18 +- .../filters/http/ext_proc/client_impl.h | 9 +- .../filters/http/ext_proc/ext_proc.cc | 173 +++++++++++- .../filters/http/ext_proc/ext_proc.h | 66 ++++- .../ext_proc/ext_proc_integration_test.cc | 222 +++++++++++++++ .../filters/http/ext_proc/filter_test.cc | 261 +++++++++++++++++- .../filters/http/ext_proc/mock_server.h | 1 + .../filters/http/ext_proc/ordering_test.cc | 103 ++++++- .../http/ext_proc/unit_test_fuzz/mocks.h | 1 + 11 files changed, 828 insertions(+), 32 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index b71129b626f9..10d8686fddec 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -36,6 +36,11 @@ behavior_changes: change: | Move ``Continue``, ``SendLocalReply`` and ``RecoverPanic` from ``FilterCallbackHandler`` to ``DecoderFilterCallbacks`` and ``EncoderFilterCallbacks``, to support full-duplex processing. +- area: ext_proc + change: | + Added support for observability mode. If enabled, each part of the HTTP request or response specified by ProcessingMode + is sent without waiting for the response from the ext_proc service. It is "Send and Go" mode that can be used by external + processor to observe Envoy data and status. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/source/extensions/filters/http/ext_proc/client.h b/source/extensions/filters/http/ext_proc/client.h index 787d946e442e..4d15fd9e161b 100644 --- a/source/extensions/filters/http/ext_proc/client.h +++ b/source/extensions/filters/http/ext_proc/client.h @@ -22,6 +22,7 @@ class ExternalProcessorStream { // Idempotent close. Return true if it actually closed. virtual bool close() PURE; virtual const StreamInfo::StreamInfo& streamInfo() const PURE; + virtual void notifyFilterDestroy() PURE; }; using ExternalProcessorStreamPtr = std::unique_ptr; diff --git a/source/extensions/filters/http/ext_proc/client_impl.cc b/source/extensions/filters/http/ext_proc/client_impl.cc index 51e60ddc423f..cc82045325ca 100644 --- a/source/extensions/filters/http/ext_proc/client_impl.cc +++ b/source/extensions/filters/http/ext_proc/client_impl.cc @@ -65,7 +65,11 @@ bool ExternalProcessorStreamImpl::close() { } void ExternalProcessorStreamImpl::onReceiveMessage(ProcessingResponsePtr&& response) { - callbacks_.onReceiveMessage(std::move(response)); + if (!callbacks_.has_value()) { + ENVOY_LOG(debug, "Underlying filter object has been destroyed."); + return; + } + callbacks_->onReceiveMessage(std::move(response)); } void ExternalProcessorStreamImpl::onCreateInitialMetadata(Http::RequestHeaderMap&) {} @@ -75,12 +79,18 @@ void ExternalProcessorStreamImpl::onReceiveTrailingMetadata(Http::ResponseTraile void ExternalProcessorStreamImpl::onRemoteClose(Grpc::Status::GrpcStatus status, const std::string& message) { ENVOY_LOG(debug, "gRPC stream closed remotely with status {}: {}", status, message); - callbacks_.logGrpcStreamInfo(); stream_closed_ = true; + + if (!callbacks_.has_value()) { + ENVOY_LOG(debug, "Underlying filter object has been destroyed."); + return; + } + + callbacks_->logGrpcStreamInfo(); if (status == Grpc::Status::Ok) { - callbacks_.onGrpcClose(); + callbacks_->onGrpcClose(); } else { - callbacks_.onGrpcError(status); + callbacks_->onGrpcError(status); } } diff --git a/source/extensions/filters/http/ext_proc/client_impl.h b/source/extensions/filters/http/ext_proc/client_impl.h index c0ed32363247..7a0a29cc4a59 100644 --- a/source/extensions/filters/http/ext_proc/client_impl.h +++ b/source/extensions/filters/http/ext_proc/client_impl.h @@ -49,6 +49,12 @@ class ExternalProcessorStreamImpl : public ExternalProcessorStream, // actually closed it. bool close() override; + void notifyFilterDestroy() override { + // When the filter object is being destroyed, `callbacks_` (which is a OptRef to filter object) + // should be reset to avoid the dangling reference. + callbacks_.reset(); + } + // AsyncStreamCallbacks void onReceiveMessage(ProcessingResponsePtr&& message) override; @@ -67,7 +73,8 @@ class ExternalProcessorStreamImpl : public ExternalProcessorStream, // if it failed to start. bool startStream(Grpc::AsyncClient&& client, const Http::AsyncClient::StreamOptions& options); - ExternalProcessorCallbacks& callbacks_; + // Optional reference to filter object. + OptRef callbacks_; Grpc::AsyncClient client_; Grpc::AsyncStream stream_; Http::AsyncClient::ParentContext grpc_context_; diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 5fe4728749ad..ba431e01e812 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -183,6 +183,7 @@ FilterConfig::FilterConfig( Extensions::Filters::Common::Expr::BuilderInstanceSharedPtr builder, Server::Configuration::CommonFactoryContext& context) : failure_mode_allow_(config.failure_mode_allow()), + observability_mode_(config.observability_mode()), route_cache_action_(config.route_cache_action()), message_timeout_(message_timeout), max_message_timeout_ms_(max_message_timeout_ms), stats_(generateStats(stats_prefix, config.stat_prefix(), scope)), @@ -305,6 +306,7 @@ FilterConfigPerRoute::FilterConfigPerRoute(const FilterConfigPerRoute& less_spec void Filter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) { Http::PassThroughFilter::setDecoderFilterCallbacks(callbacks); + filter_callbacks_ = &callbacks; decoding_state_.setDecoderFilterCallbacks(callbacks); const Envoy::StreamInfo::FilterStateSharedPtr& filter_state = callbacks.streamInfo().filterState(); @@ -352,7 +354,9 @@ Filter::StreamOpenState Filter::openStream() { } stats_.streams_started_.inc(); - stream_ = config_->threadLocalStreamManager().store(this, std::move(stream_object)); + // TODO(tyxia) Switch to address of stream + stream_ = config_->threadLocalStreamManager().store(decoder_callbacks_->streamId(), + std::move(stream_object), config_->stats()); // For custom access logging purposes. Applicable only for Envoy gRPC as Google gRPC does not // have a proper implementation of streamInfo. if (grpc_service_.has_envoy_grpc() && logging_info_ != nullptr) { @@ -369,20 +373,40 @@ void Filter::closeStream() { stats_.streams_closed_.inc(); } stream_ = nullptr; - config_->threadLocalStreamManager().erase(this); + config_->threadLocalStreamManager().erase(decoder_callbacks_->streamId()); } else { ENVOY_LOG(debug, "Stream already closed"); } } +void Filter::deferredCloseStream() { + ENVOY_LOG(debug, "Calling deferred close on stream"); + config_->threadLocalStreamManager().deferredErase(decoder_callbacks_->streamId(), + filter_callbacks_->dispatcher()); +} + void Filter::onDestroy() { ENVOY_LOG(debug, "onDestroy"); // Make doubly-sure we no longer use the stream, as // per the filter contract. processing_complete_ = true; - closeStream(); decoding_state_.stopMessageTimer(); encoding_state_.stopMessageTimer(); + + if (stream_ != nullptr) { + stream_->notifyFilterDestroy(); + } + + if (config_->observabilityMode()) { + // In observability mode where the main stream processing and side stream processing are + // asynchronous, it is possible that filter instance is destroyed before the side stream request + // arrives at ext_proc server. In order to prevent the data loss in this case, side stream + // closure is deferred upon filter destruction with a timer. + deferredCloseStream(); + } else { + // Perform immediate close on the stream otherwise. + closeStream(); + } } FilterHeadersStatus Filter::onHeaders(ProcessorState& state, @@ -399,13 +423,8 @@ FilterHeadersStatus Filter::onHeaders(ProcessorState& state, state.setHeaders(&headers); state.setHasNoBody(end_stream); - ProcessingRequest req; - addAttributes(state, req); - addDynamicMetadata(state, req); - auto* headers_req = state.mutableHeaders(req); - MutationUtils::headersToProto(headers, config_->allowedHeaders(), config_->disallowedHeaders(), - *headers_req->mutable_headers()); - headers_req->set_end_of_stream(end_stream); + ProcessingRequest req = + buildHeaderRequest(state, headers, end_stream, /*observability_mode=*/false); state.onStartProcessorCall(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout(), ProcessorState::CallbackState::HeadersCallback); ENVOY_LOG(debug, "Sending headers message"); @@ -418,6 +437,12 @@ FilterHeadersStatus Filter::onHeaders(ProcessorState& state, FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_stream) { ENVOY_LOG(trace, "decodeHeaders: end_stream = {}", end_stream); mergePerRouteConfig(); + + // Send headers in observability mode. + if (decoding_state_.sendHeaders() && config_->observabilityMode()) { + return sendHeadersInObservabilityMode(headers, decoding_state_, end_stream); + } + if (end_stream) { decoding_state_.setCompleteBodyAvailable(true); } @@ -442,6 +467,10 @@ FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_st } FilterDataStatus Filter::onData(ProcessorState& state, Buffer::Instance& data, bool end_stream) { + if (config_->observabilityMode()) { + return sendDataInObservabilityMode(data, state, end_stream); + } + if (end_stream) { state.setCompleteBodyAvailable(true); } @@ -592,6 +621,73 @@ FilterDataStatus Filter::onData(ProcessorState& state, Buffer::Instance& data, b return result; } +ProcessingRequest Filter::buildHeaderRequest(ProcessorState& state, + Http::RequestOrResponseHeaderMap& headers, + bool end_stream, bool observability_mode) { + ProcessingRequest req; + if (observability_mode) { + req.set_observability_mode(true); + } + addAttributes(state, req); + addDynamicMetadata(state, req); + auto* headers_req = state.mutableHeaders(req); + MutationUtils::headersToProto(headers, config_->allowedHeaders(), config_->disallowedHeaders(), + *headers_req->mutable_headers()); + headers_req->set_end_of_stream(end_stream); + return req; +} + +FilterHeadersStatus +Filter::sendHeadersInObservabilityMode(Http::RequestOrResponseHeaderMap& headers, + ProcessorState& state, bool end_stream) { + switch (openStream()) { + case StreamOpenState::Error: + return FilterHeadersStatus::StopIteration; + case StreamOpenState::IgnoreError: + return FilterHeadersStatus::Continue; + case StreamOpenState::Ok: + // Fall through + break; + } + + ProcessingRequest req = + buildHeaderRequest(state, headers, end_stream, /*observability_mode=*/true); + ENVOY_LOG(debug, "Sending headers message in observability mode"); + stream_->send(std::move(req), false); + stats_.stream_msgs_sent_.inc(); + + return FilterHeadersStatus::Continue; +} + +Http::FilterDataStatus Filter::sendDataInObservabilityMode(Buffer::Instance& data, + ProcessorState& state, bool end_stream) { + // For the body processing mode in observability mode, only STREAMED body processing mode is + // supported and any other body processing modes will be ignored. NONE mode(i.e., skip body + // processing) will still work as expected. + if (state.bodyMode() == ProcessingMode::STREAMED) { + // Try to open the stream if the connection has not been established. + switch (openStream()) { + case StreamOpenState::Error: + return FilterDataStatus::StopIterationNoBuffer; + case StreamOpenState::IgnoreError: + return FilterDataStatus::Continue; + case StreamOpenState::Ok: + // Fall through + break; + } + // Set up the the body chunk and send. + auto req = setupBodyChunk(state, data, end_stream); + req.set_observability_mode(true); + stream_->send(std::move(req), false); + stats_.stream_msgs_sent_.inc(); + ENVOY_LOG(debug, "Sending body message in ObservabilityMode"); + } else if (state.bodyMode() != ProcessingMode::NONE) { + ENVOY_LOG(error, "Wrong body mode for observability mode, no data is sent."); + } + + return FilterDataStatus::Continue; +} + std::pair Filter::sendStreamChunk(ProcessorState& state) { switch (openStream()) { case StreamOpenState::Error: @@ -625,6 +721,21 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap& return FilterTrailersStatus::Continue; } + // Send trailer in observability mode. + if (state.sendTrailers() && config_->observabilityMode()) { + switch (openStream()) { + case StreamOpenState::Error: + return FilterTrailersStatus::StopIteration; + case StreamOpenState::IgnoreError: + return FilterTrailersStatus::Continue; + case StreamOpenState::Ok: + // Fall through + break; + } + sendTrailers(state, trailers, /*observability_mode=*/true); + return FilterTrailersStatus::Continue; + } + bool body_delivered = state.completeBodyAvailable(); state.setCompleteBodyAvailable(true); state.setTrailersAvailable(true); @@ -701,6 +812,11 @@ FilterHeadersStatus Filter::encodeHeaders(ResponseHeaderMap& headers, bool end_s // Try to merge the route config again in case the decodeHeaders() is not called when processing // local reply. mergePerRouteConfig(); + + if (encoding_state_.sendHeaders() && config_->observabilityMode()) { + return sendHeadersInObservabilityMode(headers, encoding_state_, end_stream); + } + if (end_stream) { encoding_state_.setCompleteBodyAvailable(true); } @@ -758,8 +874,10 @@ void Filter::sendBodyChunk(ProcessorState& state, ProcessorState::CallbackState stats_.stream_msgs_sent_.inc(); } -void Filter::sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers) { +void Filter::sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers, + bool observability_mode) { ProcessingRequest req; + req.set_observability_mode(observability_mode); addAttributes(state, req); addDynamicMetadata(state, req); auto* trailers_req = state.mutableTrailers(req); @@ -772,6 +890,7 @@ void Filter::sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers stats_.stream_msgs_sent_.inc(); } +// TODO(tyxia) Add logging support for observability mode. void Filter::logGrpcStreamInfo() { if (stream_ != nullptr && logging_info_ != nullptr && grpc_service_.has_envoy_grpc()) { const auto& upstream_meter = stream_->streamInfo().getUpstreamBytesMeter(); @@ -918,6 +1037,13 @@ void Filter::setDecoderDynamicMetadata(const ProcessingResponse& response) { } void Filter::onReceiveMessage(std::unique_ptr&& r) { + + if (config_->observabilityMode()) { + ENVOY_LOG(trace, "Ignoring received message when observability mode is enabled"); + // Ignore response messages in the observability mode. + return; + } + if (processing_complete_) { ENVOY_LOG(debug, "Ignoring stream message received after processing complete"); // Ignore additional messages after we decided we were done with the stream @@ -1231,6 +1357,31 @@ void Filter::mergePerRouteConfig() { } } +void DeferredDeletableStream::closeStreamOnTimer(uint64_t stream_id) { + // Close the stream. + if (stream_) { + ENVOY_LOG(debug, "Closing the stream"); + if (stream_->close()) { + stats.streams_closed_.inc(); + } + stream_.reset(); + } else { + ENVOY_LOG(debug, "Stream already closed"); + } + + // Erase this entry from the map. + parent.erase(stream_id); +} + +// In the deferred closure mode, stream closure is deferred upon filter destruction, with a timer +// to prevent unbounded resource usage growth. +void DeferredDeletableStream::deferredClose(Envoy::Event::Dispatcher& dispatcher, + uint64_t stream_id) { + derferred_close_timer = + dispatcher.createTimer([this, stream_id] { closeStreamOnTimer(stream_id); }); + derferred_close_timer->enableTimer(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS)); +} + std::string responseCaseToString(const ProcessingResponse::ResponseCase response_case) { switch (response_case) { case ProcessingResponse::ResponseCase::kRequestHeaders: diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index d7d5d9bc2fde..51f39b2805d8 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -145,19 +145,53 @@ class ImmediateMutationChecker { std::unique_ptr rule_checker_; }; +class ThreadLocalStreamManager; +// TODO(tyxia) Make it configurable. +inline constexpr uint32_t DEFAULT_CLOSE_TIMEOUT_MS = 1000; + +// Deferred deletable stream wrapper. +struct DeferredDeletableStream : public Logger::Loggable { + explicit DeferredDeletableStream(ExternalProcessorStreamPtr stream, + ThreadLocalStreamManager& stream_manager, + const ExtProcFilterStats& stat) + : stream_(std::move(stream)), parent(stream_manager), stats(stat) {} + + void deferredClose(Envoy::Event::Dispatcher& dispatcher, uint64_t stream_id); + + void closeStreamOnTimer(uint64_t stream_id); + ExternalProcessorStreamPtr stream_; + ThreadLocalStreamManager& parent; + ExtProcFilterStats stats; + Event::TimerPtr derferred_close_timer; +}; + +using DeferredDeletableStreamPtr = std::unique_ptr; + class ThreadLocalStreamManager : public Envoy::ThreadLocal::ThreadLocalObject { public: - // Store the ExternalProcessorStreamPtr in the map and return its raw pointer. - ExternalProcessorStream* store(Filter* filter, ExternalProcessorStreamPtr stream) { - stream_manager_[filter] = std::move(stream); - return stream_manager_[filter].get(); + // Store the ExternalProcessorStreamPtr (as a wrapper object) in the map and return the raw + // pointer of ExternalProcessorStream. + ExternalProcessorStream* store(uint64_t stream_id, ExternalProcessorStreamPtr stream, + const ExtProcFilterStats& stat) { + stream_manager_[stream_id] = + std::make_unique(std::move(stream), *this, stat); + return stream_manager_[stream_id]->stream_.get(); } - void erase(Filter* filter) { stream_manager_.erase(filter); } + void erase(uint64_t stream_id) { stream_manager_.erase(stream_id); } + + void deferredErase(uint64_t stream_id, Envoy::Event::Dispatcher& dispatcher) { + auto it = stream_manager_.find(stream_id); + if (it == stream_manager_.end()) { + return; + } + + it->second->deferredClose(dispatcher, stream_id); + } private: - // Map of ExternalProcessorStreamPtrs with filter pointer as key. - absl::flat_hash_map stream_manager_; + // Map of DeferredDeletableStreamPtrs with stream id as key. + absl::flat_hash_map stream_manager_; }; class FilterConfig { @@ -171,6 +205,8 @@ class FilterConfig { bool failureModeAllow() const { return failure_mode_allow_; } + bool observabilityMode() const { return observability_mode_; } + const std::chrono::milliseconds& messageTimeout() const { return message_timeout_; } uint32_t maxMessageTimeout() const { return max_message_timeout_ms_; } @@ -231,6 +267,7 @@ class FilterConfig { return {ALL_EXT_PROC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))}; } const bool failure_mode_allow_; + const bool observability_mode_; envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor::RouteCacheAction route_cache_action_; const std::chrono::milliseconds message_timeout_; @@ -374,7 +411,8 @@ class Filter : public Logger::Loggable, void sendBodyChunk(ProcessorState& state, ProcessorState::CallbackState new_state, envoy::service::ext_proc::v3::ProcessingRequest& req); - void sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers); + void sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers, + bool observability_mode = false); bool inHeaderProcessState() { return (decoding_state_.callbackState() == ProcessorState::CallbackState::HeadersCallback || encoding_state_.callbackState() == ProcessorState::CallbackState::HeadersCallback); @@ -407,6 +445,17 @@ class Filter : public Logger::Loggable, envoy::service::ext_proc::v3::ProcessingRequest& req); void addAttributes(ProcessorState& state, envoy::service::ext_proc::v3::ProcessingRequest& req); + Http::FilterHeadersStatus + sendHeadersInObservabilityMode(Http::RequestOrResponseHeaderMap& headers, ProcessorState& state, + bool end_stream); + Http::FilterDataStatus sendDataInObservabilityMode(Buffer::Instance& data, ProcessorState& state, + bool end_stream); + void deferredCloseStream(); + + envoy::service::ext_proc::v3::ProcessingRequest + buildHeaderRequest(ProcessorState& state, Http::RequestOrResponseHeaderMap& headers, + bool end_stream, bool observability_mode); + const FilterConfigSharedPtr config_; const ExternalProcessorClientPtr client_; ExtProcFilterStats stats_; @@ -437,6 +486,7 @@ class Filter : public Logger::Loggable, std::vector untyped_forwarding_namespaces_{}; std::vector typed_forwarding_namespaces_{}; std::vector untyped_receiving_namespaces_{}; + Http::StreamFilterCallbacks* filter_callbacks_; }; extern std::string responseCaseToString( diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 5196a469701b..df31b44dd1b5 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -9,6 +9,7 @@ #include "envoy/service/ext_proc/v3/external_processor.pb.h" #include "source/extensions/filters/http/ext_proc/config.h" +#include "source/extensions/filters/http/ext_proc/ext_proc.h" #include "test/common/http/common.h" #include "test/extensions/filters/http/ext_proc/logging_test_filter.pb.h" @@ -43,6 +44,7 @@ using envoy::service::ext_proc::v3::ImmediateResponse; using envoy::service::ext_proc::v3::ProcessingRequest; using envoy::service::ext_proc::v3::ProcessingResponse; using envoy::service::ext_proc::v3::TrailersResponse; +using Extensions::HttpFilters::ExternalProcessing::DEFAULT_CLOSE_TIMEOUT_MS; using Extensions::HttpFilters::ExternalProcessing::HasNoHeader; using Extensions::HttpFilters::ExternalProcessing::HeaderProtosEqual; using Extensions::HttpFilters::ExternalProcessing::makeHeaderValue; @@ -256,6 +258,21 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, return codec_client_->makeRequestWithBody(headers, std::string(body)); } + IntegrationStreamDecoderPtr sendDownstreamRequestWithBodyAndTrailer(absl::string_view body) { + codec_client_ = makeHttpConnection(lookupPort("http")); + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + codec_client_->sendData(*request_encoder_, body, false); + Http::TestRequestTrailerMapImpl request_trailers{{"x-trailer-foo", "yes"}}; + codec_client_->sendTrailers(*request_encoder_, request_trailers); + + return response; + } + void verifyDownstreamResponse(IntegrationStreamDecoder& response, int status_code) { ASSERT_TRUE(response.waitForEndStream()); EXPECT_TRUE(response.complete()); @@ -4086,4 +4103,209 @@ TEST_P(ExtProcIntegrationTest, RetryOnDifferentHost) { verifyDownstreamResponse(*response, 200); } +TEST_P(ExtProcIntegrationTest, ObservabilityModeWithHeader) { + proto_config_.set_observability_mode(true); + initializeConfig(); + HttpIntegrationTest::initialize(); + + auto response = sendDownstreamRequest(absl::nullopt); + Http::TestRequestHeaderMapImpl expected_request_headers{{":scheme", "http"}, + {":method", "GET"}, + {"host", "host"}, + {":path", "/"}, + {"x-forwarded-proto", "http"}}; + processRequestHeadersMessage( + *grpc_upstreams_[0], true, + [&expected_request_headers](const HttpHeaders& headers, HeadersResponse& headers_resp) { + // Verify the header request. + EXPECT_THAT(headers.headers(), HeaderProtosEqual(expected_request_headers)); + EXPECT_TRUE(headers.end_of_stream()); + + // Try to mutate the header. + auto* response_mutation = headers_resp.mutable_response()->mutable_header_mutation(); + auto* add1 = response_mutation->add_set_headers(); + add1->mutable_header()->set_key("x-response-processed"); + add1->mutable_header()->set_value("1"); + return true; + }); + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); + // Header mutation response has been ignored. + EXPECT_THAT(upstream_request_->headers(), HasNoHeader("x-remove-this")); + + Http::TestResponseHeaderMapImpl response_headers = + Http::TestResponseHeaderMapImpl{{":status", "200"}}; + upstream_request_->encodeHeaders(response_headers, false); + upstream_request_->encodeData(100, true); + + processResponseHeadersMessage( + *grpc_upstreams_[0], false, [](const HttpHeaders& headers, HeadersResponse&) { + Http::TestRequestHeaderMapImpl expected_response_headers{{":status", "200"}}; + EXPECT_THAT(headers.headers(), HeaderProtosEqual(expected_response_headers)); + return true; + }); + + verifyDownstreamResponse(*response, 200); + + timeSystem().advanceTimeWaitImpl(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS)); +} + +TEST_P(ExtProcIntegrationTest, ObservabilityModeWithBody) { + proto_config_.set_observability_mode(true); + + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::STREAMED); + proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_response_body_mode(ProcessingMode::STREAMED); + + initializeConfig(); + HttpIntegrationTest::initialize(); + const std::string original_body_str = "Hello"; + auto response = sendDownstreamRequestWithBody(original_body_str, absl::nullopt); + + processRequestBodyMessage( + *grpc_upstreams_[0], true, [](const HttpBody& body, BodyResponse& body_resp) { + EXPECT_TRUE(body.end_of_stream()); + // Try to mutate the body. + auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); + body_mut->set_body("Hello, World!"); + return true; + }); + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); + // Body mutation response has been ignored. + EXPECT_EQ(upstream_request_->body().toString(), original_body_str); + + Http::TestResponseHeaderMapImpl response_headers = + Http::TestResponseHeaderMapImpl{{":status", "200"}}; + upstream_request_->encodeHeaders(response_headers, false); + upstream_request_->encodeData(100, true); + + processResponseBodyMessage(*grpc_upstreams_[0], false, [](const HttpBody& body, BodyResponse&) { + EXPECT_TRUE(body.end_of_stream()); + return true; + }); + + verifyDownstreamResponse(*response, 200); + + timeSystem().advanceTimeWaitImpl(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS)); +} + +TEST_P(ExtProcIntegrationTest, ObservabilityModeWithWrongBodyMode) { + proto_config_.set_observability_mode(true); + + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::BUFFERED); + proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); + + initializeConfig(); + HttpIntegrationTest::initialize(); + const std::string original_body_str = "Hello"; + auto response = sendDownstreamRequestWithBody(original_body_str, absl::nullopt); + + handleUpstreamRequest(); + verifyDownstreamResponse(*response, 200); + + timeSystem().advanceTimeWaitImpl(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS)); +} + +TEST_P(ExtProcIntegrationTest, ObservabilityModeWithTrailer) { + proto_config_.set_observability_mode(true); + + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_response_trailer_mode(ProcessingMode::SEND); + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + + handleUpstreamRequestWithTrailer(); + + processResponseTrailersMessage( + *grpc_upstreams_[0], true, [](const HttpTrailers& trailers, TrailersResponse& resp) { + // Verify the trailer + Http::TestResponseTrailerMapImpl expected_trailers{{"x-test-trailers", "Yes"}}; + EXPECT_THAT(trailers.trailers(), HeaderProtosEqual(expected_trailers)); + + // Try to mutate the trailer + auto* trailer_mut = resp.mutable_header_mutation(); + auto* trailer_add = trailer_mut->add_set_headers(); + trailer_add->mutable_header()->set_key("x-modified-trailers"); + trailer_add->mutable_header()->set_value("xxx"); + return true; + }); + + verifyDownstreamResponse(*response, 200); + EXPECT_THAT(*(response->trailers()), HasNoHeader("x-modified-trailers")); + + timeSystem().advanceTimeWaitImpl(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS)); +} + +TEST_P(ExtProcIntegrationTest, ObservabilityModeWithFullRequest) { + proto_config_.set_observability_mode(true); + + proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::STREAMED); + proto_config_.mutable_processing_mode()->set_request_trailer_mode(ProcessingMode::SEND); + proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); + + initializeConfig(); + HttpIntegrationTest::initialize(); + const std::string body_str = "Hello"; + auto response = sendDownstreamRequestWithBodyAndTrailer(body_str); + + processRequestHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); + processRequestBodyMessage(*grpc_upstreams_[0], false, absl::nullopt); + processRequestTrailersMessage(*grpc_upstreams_[0], false, absl::nullopt); + + handleUpstreamRequest(); + verifyDownstreamResponse(*response, 200); + + timeSystem().advanceTimeWaitImpl(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS)); +} + +TEST_P(ExtProcIntegrationTest, ObservabilityModeWithFullResponse) { + proto_config_.set_observability_mode(true); + + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_response_body_mode(ProcessingMode::STREAMED); + proto_config_.mutable_processing_mode()->set_response_trailer_mode(ProcessingMode::SEND); + + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + + handleUpstreamRequestWithTrailer(); + + processResponseHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); + processResponseBodyMessage(*grpc_upstreams_[0], false, absl::nullopt); + processResponseTrailersMessage(*grpc_upstreams_[0], false, absl::nullopt); + + verifyDownstreamResponse(*response, 200); + + timeSystem().advanceTimeWaitImpl(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS)); +} + +TEST_P(ExtProcIntegrationTest, InvalidServerOnResponseInObservabilityMode) { + proto_config_.set_observability_mode(true); + + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_response_body_mode(ProcessingMode::STREAMED); + + ConfigOptions config_option = {}; + config_option.valid_grpc_server = false; + initializeConfig(config_option); + HttpIntegrationTest::initialize(); + + auto response = sendDownstreamRequestWithBody("Replace this!", absl::nullopt); + + handleUpstreamRequest(); + EXPECT_FALSE(grpc_upstreams_[0]->waitForHttpConnection(*dispatcher_, processor_connection_, + std::chrono::milliseconds(25000))); + timeSystem().advanceTimeWaitImpl(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS)); +} + } // namespace Envoy diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index eadedf1f26e7..eb075f77ab2f 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -187,7 +187,7 @@ class HttpFilterTest : public testing::Test { stream_callbacks_ = &callbacks; - auto stream = std::make_unique(); + auto stream = std::make_unique>(); // We never send with the "close" flag set EXPECT_CALL(*stream, send(_, false)).WillRepeatedly(Invoke(this, &HttpFilterTest::doSend)); @@ -261,14 +261,21 @@ class HttpFilterTest : public testing::Test { bool buffering_data, absl::optional> cb) { - EXPECT_FALSE(last_request_.observability_mode()); ASSERT_TRUE(last_request_.has_request_headers()); const auto& headers = last_request_.request_headers(); + auto response = std::make_unique(); auto* headers_response = response->mutable_request_headers(); if (cb) { (*cb)(headers, *response, *headers_response); } + + if (observability_mode_) { + EXPECT_TRUE(last_request_.observability_mode()); + return; + } + + EXPECT_FALSE(last_request_.observability_mode()); if (!buffering_data) { EXPECT_CALL(decoder_callbacks_, continueDecoding()); } @@ -281,7 +288,6 @@ class HttpFilterTest : public testing::Test { bool buffering_data, absl::optional> cb) { - EXPECT_FALSE(last_request_.observability_mode()); ASSERT_TRUE(last_request_.has_response_headers()); const auto& headers = last_request_.response_headers(); auto response = std::make_unique(); @@ -289,6 +295,13 @@ class HttpFilterTest : public testing::Test { if (cb) { (*cb)(headers, *response, *headers_response); } + + if (observability_mode_) { + EXPECT_TRUE(last_request_.observability_mode()); + return; + } + + EXPECT_FALSE(last_request_.observability_mode()); if (!buffering_data) { EXPECT_CALL(encoder_callbacks_, continueEncoding()); } @@ -301,8 +314,14 @@ class HttpFilterTest : public testing::Test { absl::optional> cb, bool should_continue = true, const std::chrono::microseconds latency = std::chrono::microseconds(10)) { - EXPECT_FALSE(last_request_.observability_mode()); ASSERT_TRUE(last_request_.has_request_body()); + + if (observability_mode_) { + EXPECT_TRUE(last_request_.observability_mode()); + return; + } + + EXPECT_FALSE(last_request_.observability_mode()); const auto& body = last_request_.request_body(); auto response = std::make_unique(); auto* body_response = response->mutable_request_body(); @@ -320,8 +339,14 @@ class HttpFilterTest : public testing::Test { void processResponseBody( absl::optional> cb, bool should_continue = true) { - EXPECT_FALSE(last_request_.observability_mode()); ASSERT_TRUE(last_request_.has_response_body()); + + if (observability_mode_) { + EXPECT_TRUE(last_request_.observability_mode()); + return; + } + + EXPECT_FALSE(last_request_.observability_mode()); const auto& body = last_request_.response_body(); auto response = std::make_unique(); auto* body_response = response->mutable_response_body(); @@ -340,8 +365,14 @@ class HttpFilterTest : public testing::Test { std::function> cb, bool should_continue = true) { - EXPECT_FALSE(last_request_.observability_mode()); ASSERT_TRUE(last_request_.has_request_trailers()); + + if (observability_mode_) { + EXPECT_TRUE(last_request_.observability_mode()); + return; + } + + EXPECT_FALSE(last_request_.observability_mode()); const auto& trailers = last_request_.request_trailers(); auto response = std::make_unique(); auto* trailers_response = response->mutable_request_trailers(); @@ -360,8 +391,13 @@ class HttpFilterTest : public testing::Test { std::function> cb, bool should_continue = true) { - EXPECT_FALSE(last_request_.observability_mode()); ASSERT_TRUE(last_request_.has_response_trailers()); + if (observability_mode_) { + EXPECT_TRUE(last_request_.observability_mode()); + return; + } + EXPECT_FALSE(last_request_.observability_mode()); + const auto& trailers = last_request_.response_trailers(); auto response = std::make_unique(); auto* trailers_response = response->mutable_response_trailers(); @@ -561,6 +597,7 @@ class HttpFilterTest : public testing::Test { ExternalProcessorCallbacks* stream_callbacks_ = nullptr; ProcessingRequest last_request_; bool server_closed_stream_ = false; + bool observability_mode_ = false; testing::NiceMock stats_store_; FilterConfigSharedPtr config_; std::shared_ptr filter_; @@ -575,6 +612,7 @@ class HttpFilterTest : public testing::Test { TestRequestTrailerMapImpl request_trailers_; TestResponseTrailerMapImpl response_trailers_; std::vector timers_; + Event::MockTimer* deferred_close_timer_; TestScopedRuntime scoped_runtime_; Envoy::Event::SimulatedTimeSystem* test_time_; envoy::config::core::v3::Metadata dynamic_metadata_; @@ -4003,6 +4041,215 @@ TEST_F(HttpFilterTest, PostAndRespondImmediatelyUpstream) { EXPECT_EQ(config_->stats().streams_closed_.value(), 1); } +TEST_F(HttpFilterTest, HeaderProcessingInObservabilityMode) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + observability_mode: true + )EOF"); + + EXPECT_TRUE(config_->observabilityMode()); + observability_mode_ = true; + + // Create synthetic HTTP request + request_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + request_headers_.addCopy(LowerCaseString("content-length"), 10); + request_headers_.addCopy(LowerCaseString("x-some-other-header"), "yes"); + + // In the observability mode, the filter returns `Continue` in all events of http request + // lifecycle. + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); + processRequestHeaders(false, + [](const HttpHeaders& header_req, ProcessingResponse&, HeadersResponse&) { + EXPECT_FALSE(header_req.end_of_stream()); + TestRequestHeaderMapImpl expected{{":path", "/"}, + {":method", "POST"}, + {":scheme", "http"}, + {"host", "host"}, + {"content-type", "text/plain"}, + {"content-length", "10"}, + {"x-some-other-header", "yes"}}; + EXPECT_THAT(header_req.headers(), HeaderProtosEqual(expected)); + }); + + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + response_headers_.addCopy(LowerCaseString(":status"), "200"); + response_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + response_headers_.addCopy(LowerCaseString("content-length"), "3"); + + // In the observability mode, the filter returns `Continue` in all events of http response + // lifecycle. + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, false)); + processResponseHeaders( + false, [](const HttpHeaders& header_resp, ProcessingResponse&, HeadersResponse&) { + EXPECT_FALSE(header_resp.end_of_stream()); + TestRequestHeaderMapImpl expected_response{ + {":status", "200"}, {"content-type", "text/plain"}, {"content-length", "3"}}; + EXPECT_THAT(header_resp.headers(), HeaderProtosEqual(expected_response)); + }); + + Buffer::OwnedImpl resp_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + + deferred_close_timer_ = new Event::MockTimer(&dispatcher_); + // Deferred close timer is expected to be enabled by `DeferredDeletableStream`'s deferredClose(), + // which is triggered by filter onDestroy() function below. + EXPECT_CALL(*deferred_close_timer_, + enableTimer(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS), _)); + filter_->onDestroy(); + deferred_close_timer_->invokeCallback(); + + EXPECT_EQ(1, config_->stats().streams_started_.value()); + // Two messages (request and response header message) are sent. + EXPECT_EQ(2, config_->stats().stream_msgs_sent_.value()); + // No response is received in observability mode. + EXPECT_EQ(0, config_->stats().stream_msgs_received_.value()); + // Deferred stream is closed. + EXPECT_EQ(1, config_->stats().streams_closed_.value()); +} + +TEST_F(HttpFilterTest, StreamingBodiesInObservabilityMode) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + observability_mode: true + processing_mode: + request_body_mode: "STREAMED" + response_body_mode: "STREAMED" + )EOF"); + + uint32_t content_length = 100; + observability_mode_ = true; + + // Create synthetic HTTP request + HttpTestUtility::addDefaultHeaders(request_headers_); + request_headers_.setMethod("POST"); + request_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + request_headers_.addCopy(LowerCaseString("content-length"), content_length); + + EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillRepeatedly(Return(nullptr)); + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); + processRequestHeaders(false, absl::nullopt); + // In observability mode, content length is not removed as there is no mutation from ext_proc + // server. + EXPECT_EQ(request_headers_.getContentLengthValue(), absl::StrCat(content_length)); + + Buffer::OwnedImpl want_request_body; + Buffer::OwnedImpl got_request_body; + EXPECT_CALL(decoder_callbacks_, injectDecodedDataToFilterChain(_, true)) + .WillRepeatedly(Invoke( + [&got_request_body](Buffer::Instance& data, Unused) { got_request_body.move(data); })); + + Buffer::OwnedImpl req_chunk_1; + TestUtility::feedBufferWithRandomCharacters(req_chunk_1, 100); + want_request_body.add(req_chunk_1.toString()); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_chunk_1, true)); + got_request_body.move(req_chunk_1); + EXPECT_EQ(want_request_body.toString(), got_request_body.toString()); + + response_headers_.addCopy(LowerCaseString(":status"), "200"); + response_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + response_headers_.addCopy(LowerCaseString("content-length"), content_length); + + EXPECT_CALL(encoder_callbacks_, encodingBuffer()).WillRepeatedly(Return(nullptr)); + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, false)); + processResponseHeaders(false, absl::nullopt); + EXPECT_EQ(response_headers_.getContentLengthValue(), absl::StrCat(content_length)); + + Buffer::OwnedImpl want_response_body; + Buffer::OwnedImpl got_response_body; + EXPECT_CALL(encoder_callbacks_, injectEncodedDataToFilterChain(_, _)) + .WillRepeatedly(Invoke( + [&got_response_body](Buffer::Instance& data, Unused) { got_response_body.move(data); })); + + for (int i = 0; i < 5; i++) { + Buffer::OwnedImpl resp_chunk; + TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); + want_response_body.add(resp_chunk.toString()); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + got_response_body.move(resp_chunk); + } + + Buffer::OwnedImpl last_resp_chunk; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(last_resp_chunk, true)); + + // At this point, since we injected the data from each chunk after the "encodeData" + // callback, and since we also injected any chunks inserted using "injectEncodedData," + // the two buffers should match! + EXPECT_EQ(want_response_body.toString(), got_response_body.toString()); + + deferred_close_timer_ = new Event::MockTimer(&dispatcher_); + // Deferred close timer is expected to be enabled by `DeferredDeletableStream`'s deferredClose(), + // which is triggered by filter onDestroy() function. + EXPECT_CALL(*deferred_close_timer_, + enableTimer(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS), _)); + filter_->onDestroy(); + deferred_close_timer_->invokeCallback(); + + EXPECT_EQ(1, config_->stats().streams_started_.value()); + EXPECT_EQ(9, config_->stats().stream_msgs_sent_.value()); + EXPECT_EQ(0, config_->stats().stream_msgs_received_.value()); + EXPECT_EQ(1, config_->stats().streams_closed_.value()); +} + +TEST_F(HttpFilterTest, StreamingAllDataInObservabilityMode) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + observability_mode: true + processing_mode: + request_header_mode: "SEND" + response_header_mode: "SEND" + request_body_mode: "STREAMED" + response_body_mode: "STREAMED" + request_trailer_mode: "SEND" + response_trailer_mode: "SEND" + )EOF"); + + observability_mode_ = true; + + HttpTestUtility::addDefaultHeaders(request_headers_); + request_headers_.setMethod("POST"); + EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillRepeatedly(Return(nullptr)); + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); + processRequestHeaders(false, absl::nullopt); + + const uint32_t chunk_number = 20; + sendChunkRequestData(chunk_number, true); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + processRequestTrailers(absl::nullopt); + + response_headers_.addCopy(LowerCaseString(":status"), "200"); + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, false)); + processResponseHeaders(true, absl::nullopt); + sendChunkResponseData(chunk_number * 2, true); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + processResponseTrailers(absl::nullopt, false); + deferred_close_timer_ = new Event::MockTimer(&dispatcher_); + // Deferred close timer is expected to be enabled by `DeferredDeletableStream`'s deferredClose(), + // which is triggered by filter onDestroy() function. + EXPECT_CALL(*deferred_close_timer_, + enableTimer(std::chrono::milliseconds(DEFAULT_CLOSE_TIMEOUT_MS), _)); + filter_->onDestroy(); + deferred_close_timer_->invokeCallback(); + + EXPECT_EQ(1, config_->stats().streams_started_.value()); + // Total gRPC messages include two headers and two trailers on top of the req/resp chunk data. + uint32_t total_msg = 3 * chunk_number + 4; + EXPECT_EQ(total_msg, config_->stats().stream_msgs_sent_.value()); + EXPECT_EQ(0, config_->stats().stream_msgs_received_.value()); + EXPECT_EQ(1, config_->stats().streams_closed_.value()); +} + class HttpFilter2Test : public HttpFilterTest, public ::Envoy::Http::HttpConnectionManagerImplMixin {}; diff --git a/test/extensions/filters/http/ext_proc/mock_server.h b/test/extensions/filters/http/ext_proc/mock_server.h index 5368d12de145..14fbb83b895e 100644 --- a/test/extensions/filters/http/ext_proc/mock_server.h +++ b/test/extensions/filters/http/ext_proc/mock_server.h @@ -25,6 +25,7 @@ class MockStream : public ExternalProcessorStream { MOCK_METHOD(void, send, (envoy::service::ext_proc::v3::ProcessingRequest&&, bool)); MOCK_METHOD(bool, close, ()); MOCK_METHOD(const StreamInfo::StreamInfo&, streamInfo, (), (const override)); + MOCK_METHOD(void, notifyFilterDestroy, ()); }; } // namespace ExternalProcessing diff --git a/test/extensions/filters/http/ext_proc/ordering_test.cc b/test/extensions/filters/http/ext_proc/ordering_test.cc index 273a5cfab987..1f8720964ccb 100644 --- a/test/extensions/filters/http/ext_proc/ordering_test.cc +++ b/test/extensions/filters/http/ext_proc/ordering_test.cc @@ -88,7 +88,7 @@ class OrderingTest : public testing::Test { const Grpc::GrpcServiceConfigWithHashKey&, const Envoy::Http::AsyncClient::StreamOptions&) { stream_callbacks_ = &callbacks; - auto stream = std::make_unique(); + auto stream = std::make_unique>(); EXPECT_CALL(*stream, send(_, _)).WillRepeatedly(Invoke(this, &OrderingTest::doSend)); EXPECT_CALL(*stream, streamInfo()).WillRepeatedly(ReturnRef(async_client_stream_info_)); EXPECT_CALL(*stream, close()); @@ -1136,6 +1136,107 @@ TEST_F(FastFailOrderingTest, GrpcErrorIgnoredOnNotSendResponseTrailer) { EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); } +class ObservabilityModeFastFailOrderingTest : public FastFailOrderingTest {}; +// gRPC failure while opening stream +TEST_F(ObservabilityModeFastFailOrderingTest, GrpcErrorOnStartRequestHeaders) { + initialize([](ExternalProcessor& cfg) { cfg.set_observability_mode(true); }); + HttpTestUtility::addDefaultHeaders(request_headers_); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, true)); +} + +// gRPC failure while opening stream with errors ignored +TEST_F(ObservabilityModeFastFailOrderingTest, GrpcErrorIgnoredOnStartRequestHeaders) { + initialize([](ExternalProcessor& cfg) { + cfg.set_observability_mode(true); + cfg.set_failure_mode_allow(true); + }); + sendRequestHeadersGet(false); + sendResponseHeaders(false); + Buffer::OwnedImpl resp_body("Hello!"); + Buffer::OwnedImpl resp_buf; + expectBufferedRequest(resp_buf); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); +} + +// gRPC failure while opening stream with only request body enabled in streaming mode +TEST_F(ObservabilityModeFastFailOrderingTest, GrpcErrorOnStartRequestBodyStreaming) { + initialize([](ExternalProcessor& cfg) { + cfg.set_observability_mode(true); + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_header_mode(ProcessingMode::SKIP); + pm->set_request_body_mode(ProcessingMode::STREAMED); + }); + sendRequestHeadersPost(false); + Buffer::OwnedImpl req_body("Hello!"); + Buffer::OwnedImpl buffered_body; + expectBufferedRequest(buffered_body); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_body, true)); +} + +// gRPC failure while opening stream with only request body enabled in streamed mode and errors +// ignored +TEST_F(ObservabilityModeFastFailOrderingTest, GrpcErrorIgnoredOnStartRequestBodyStreamed) { + initialize([](ExternalProcessor& cfg) { + cfg.set_observability_mode(true); + cfg.set_failure_mode_allow(true); + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_header_mode(ProcessingMode::SKIP); + pm->set_request_body_mode(ProcessingMode::STREAMED); + }); + sendRequestHeadersPost(false); + Buffer::OwnedImpl req_body("Hello!"); + Buffer::OwnedImpl buffered_body; + expectBufferedRequest(buffered_body); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_body, true)); + sendResponseHeaders(false); + Buffer::OwnedImpl resp_body("Hello!"); + Buffer::OwnedImpl resp_buf; + expectBufferedRequest(resp_buf); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); +} + +// gRPC failure while opening stream with only response trailer enabled +TEST_F(ObservabilityModeFastFailOrderingTest, GrpcErrorOnStartResponseTrailers) { + initialize([](ExternalProcessor& cfg) { + cfg.set_observability_mode(true); + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_header_mode(ProcessingMode::SKIP); + pm->set_response_header_mode(ProcessingMode::SKIP); + pm->set_response_trailer_mode(ProcessingMode::SEND); + }); + + sendRequestHeadersGet(false); + sendResponseHeaders(false); + Buffer::OwnedImpl resp_body("Hello!"); + Buffer::OwnedImpl resp_buf; + expectBufferedResponse(resp_buf); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, false)); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); + EXPECT_EQ(FilterTrailersStatus::StopIteration, filter_->encodeTrailers(response_trailers_)); +} + +// gRPC failure while opening stream with only response trailer enabled but errors ignored +TEST_F(ObservabilityModeFastFailOrderingTest, GrpcErrorIgnoredOnStartResponseTrailers) { + initialize([](ExternalProcessor& cfg) { + cfg.set_observability_mode(true); + cfg.set_failure_mode_allow(true); + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_header_mode(ProcessingMode::SKIP); + pm->set_response_header_mode(ProcessingMode::SKIP); + pm->set_response_trailer_mode(ProcessingMode::SEND); + }); + + sendRequestHeadersGet(false); + sendResponseHeaders(false); + Buffer::OwnedImpl resp_body("Hello!"); + Buffer::OwnedImpl resp_buf; + expectBufferedResponse(resp_buf); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, false)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); +} + } // namespace } // namespace ExternalProcessing } // namespace HttpFilters diff --git a/test/extensions/filters/http/ext_proc/unit_test_fuzz/mocks.h b/test/extensions/filters/http/ext_proc/unit_test_fuzz/mocks.h index 17c31784d9e4..4f45cfc1ba9e 100644 --- a/test/extensions/filters/http/ext_proc/unit_test_fuzz/mocks.h +++ b/test/extensions/filters/http/ext_proc/unit_test_fuzz/mocks.h @@ -21,6 +21,7 @@ class MockStream : public ExternalProcessing::ExternalProcessorStream { (envoy::service::ext_proc::v3::ProcessingRequest && request, bool end_stream)); MOCK_METHOD(bool, close, ()); MOCK_METHOD(const StreamInfo::StreamInfo&, streamInfo, (), (const override)); + MOCK_METHOD(void, notifyFilterDestroy, ()); }; class MockClient : public ExternalProcessing::ExternalProcessorClient { From f99174b4d29a23a436dc4276046989d6f98d1b4d Mon Sep 17 00:00:00 2001 From: yanjunxiang-google <78807980+yanjunxiang-google@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:29:06 -0400 Subject: [PATCH 05/31] Support sampling behavior when composite filter executing action (#34611) Signed-off-by: Yanjun Xiang --- .../filters/http/composite/v3/composite.proto | 10 + changelogs/current.yaml | 5 + .../filters/http/composite/action.cc | 51 +++-- .../filters/http/composite/action.h | 39 +++- .../filters/http/match_delegate/config.cc | 2 +- .../composite_filter_integration_test.cc | 205 ++++++++++++------ .../filters/http/composite/filter_test.cc | 69 ++++++ 7 files changed, 291 insertions(+), 90 deletions(-) diff --git a/api/envoy/extensions/filters/http/composite/v3/composite.proto b/api/envoy/extensions/filters/http/composite/v3/composite.proto index 8ea4f2a43d6c..4e7d372ab99b 100644 --- a/api/envoy/extensions/filters/http/composite/v3/composite.proto +++ b/api/envoy/extensions/filters/http/composite/v3/composite.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package envoy.extensions.filters.http.composite.v3; +import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/config_source.proto"; import "envoy/config/core/v3/extension.proto"; @@ -57,4 +58,13 @@ message ExecuteFilterAction { // Only one of ``typed_config`` or ``dynamic_config`` can be set. DynamicConfig dynamic_config = 2 [(udpa.annotations.field_migrate).oneof_promotion = "config_type"]; + + // Probability of the action execution. If not specified, this is 100%. + // This allows sampling behavior for the configured actions. + // For example, if + // :ref:`default_value ` + // under the ``sample_percent`` is configured with 30%, a dice roll with that + // probability is done. The underline action will only be executed if the + // dice roll returns positive. Otherwise, the action is skipped. + config.core.v3.RuntimeFractionalPercent sample_percent = 3; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 10d8686fddec..d1f51c655210 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -32,6 +32,11 @@ behavior_changes: :ref:`TlvsMetadata type `. This change can be temporarily disabled by setting the runtime flag ``envoy.reloadable_features.use_typed_metadata_in_proxy_protocol_listener`` to ``false``. +- area: composite_filter + change: | + Adding support for + :ref:`sample_percent `. + It specifies the probability of the action execution. If not specified, it is 100%. - area: golang change: | Move ``Continue``, ``SendLocalReply`` and ``RecoverPanic` from ``FilterCallbackHandler`` to ``DecoderFilterCallbacks`` and diff --git a/source/extensions/filters/http/composite/action.cc b/source/extensions/filters/http/composite/action.cc index df2910956c96..27cfe9e6f28e 100644 --- a/source/extensions/filters/http/composite/action.cc +++ b/source/extensions/filters/http/composite/action.cc @@ -9,6 +9,17 @@ void ExecuteFilterAction::createFilters(Http::FilterChainFactoryCallbacks& callb cb_(callbacks); } +bool ExecuteFilterActionFactory::isSampled( + const envoy::extensions::filters::http::composite::v3::ExecuteFilterAction& composite_action, + Envoy::Runtime::Loader& runtime) { + if (composite_action.has_sample_percent() && + !runtime.snapshot().featureEnabled(composite_action.sample_percent().runtime_key(), + composite_action.sample_percent().default_value())) { + return false; + } + return true; +} + Matcher::ActionFactoryCb ExecuteFilterActionFactory::createActionFactoryCb( const Protobuf::Message& config, Http::Matching::HttpFilterActionContext& context, ProtobufMessage::ValidationVisitor& validation_visitor) { @@ -66,6 +77,28 @@ Matcher::ActionFactoryCb ExecuteFilterActionFactory::createDynamicActionFactoryC provider_manager); } +Matcher::ActionFactoryCb ExecuteFilterActionFactory::createActionFactoryCbCommon( + const envoy::extensions::filters::http::composite::v3::ExecuteFilterAction& composite_action, + Http::Matching::HttpFilterActionContext& context, Envoy::Http::FilterFactoryCb& callback, + bool is_downstream) { + const std::string stream_str = is_downstream ? "downstream" : "upstream"; + + if (callback == nullptr) { + throw EnvoyException( + fmt::format("Failed to get {} filter factory creation function", stream_str)); + } + std::string name = composite_action.typed_config().name(); + ASSERT(context.server_factory_context_ != absl::nullopt); + Envoy::Runtime::Loader& runtime = context.server_factory_context_->runtime(); + return [cb = std::move(callback), n = std::move(name), + composite_action = std::move(composite_action), &runtime, this]() -> Matcher::ActionPtr { + if (!isSampled(composite_action, runtime)) { + return nullptr; + } + return std::make_unique(cb, n); + }; +} + Matcher::ActionFactoryCb ExecuteFilterActionFactory::createStaticActionFactoryCbDownstream( const envoy::extensions::filters::http::composite::v3::ExecuteFilterAction& composite_action, Http::Matching::HttpFilterActionContext& context, @@ -93,14 +126,7 @@ Matcher::ActionFactoryCb ExecuteFilterActionFactory::createStaticActionFactoryCb *message, context.stat_prefix_, context.server_factory_context_.value()); } - if (callback == nullptr) { - throw EnvoyException("Failed to get downstream filter factory creation function"); - } - std::string name = composite_action.typed_config().name(); - - return [cb = std::move(callback), n = std::move(name)]() -> Matcher::ActionPtr { - return std::make_unique(cb, n); - }; + return createActionFactoryCbCommon(composite_action, context, callback, true); } Matcher::ActionFactoryCb ExecuteFilterActionFactory::createStaticActionFactoryCbUpstream( @@ -124,14 +150,7 @@ Matcher::ActionFactoryCb ExecuteFilterActionFactory::createStaticActionFactoryCb callback = callback_or_status.value(); } - if (callback == nullptr) { - throw EnvoyException("Failed to get upstream filter factory creation function"); - } - std::string name = composite_action.typed_config().name(); - - return [cb = std::move(callback), n = std::move(name)]() -> Matcher::ActionPtr { - return std::make_unique(cb, n); - }; + return createActionFactoryCbCommon(composite_action, context, callback, false); } REGISTER_FACTORY(ExecuteFilterActionFactory, diff --git a/source/extensions/filters/http/composite/action.h b/source/extensions/filters/http/composite/action.h index 1d062aebf4c2..dd06a0a924b9 100644 --- a/source/extensions/filters/http/composite/action.h +++ b/source/extensions/filters/http/composite/action.h @@ -45,7 +45,18 @@ class ExecuteFilterActionFactory return std::make_unique(); } + // Rolling the dice to decide whether the action will be sampled. + // By default, if sample_percent is not specified, then it is sampled. + bool isSampled( + const envoy::extensions::filters::http::composite::v3::ExecuteFilterAction& composite_action, + Envoy::Runtime::Loader& runtime); + private: + Matcher::ActionFactoryCb createActionFactoryCbCommon( + const envoy::extensions::filters::http::composite::v3::ExecuteFilterAction& composite_action, + Http::Matching::HttpFilterActionContext& context, Envoy::Http::FilterFactoryCb& callback, + bool is_downstream); + template Matcher::ActionFactoryCb createDynamicActionFactoryCbTyped( const envoy::extensions::filters::http::composite::v3::ExecuteFilterAction& composite_action, @@ -60,16 +71,24 @@ class ExecuteFilterActionFactory provider_manager->createDynamicFilterConfigProvider( config_discovery, name, server_factory_context, factory_context, server_factory_context.clusterManager(), false, filter_chain_type, nullptr); - return [provider = std::move(provider), n = std::move(name)]() -> Matcher::ActionPtr { - auto config_value = provider->config(); - if (config_value.has_value()) { - auto factory_cb = config_value.value().get().factory_cb; - return std::make_unique(factory_cb, n); - } - // There is no dynamic config available. Apply missing config filter. - auto factory_cb = Envoy::Http::MissingConfigFilterFactory; - return std::make_unique(factory_cb, n); - }; + + Envoy::Runtime::Loader& runtime = context.server_factory_context_->runtime(); + return + [provider = std::move(provider), n = std::move(name), + composite_action = std::move(composite_action), &runtime, this]() -> Matcher::ActionPtr { + if (!isSampled(composite_action, runtime)) { + return nullptr; + } + + auto config_value = provider->config(); + if (config_value.has_value()) { + auto factory_cb = config_value.value().get().factory_cb; + return std::make_unique(factory_cb, n); + } + // There is no dynamic config available. Apply missing config filter. + auto factory_cb = Envoy::Http::MissingConfigFilterFactory; + return std::make_unique(factory_cb, n); + }; } Matcher::ActionFactoryCb createDynamicActionFactoryCbDownstream( diff --git a/source/extensions/filters/http/match_delegate/config.cc b/source/extensions/filters/http/match_delegate/config.cc index a0eeac644a14..e944c7b47bcb 100644 --- a/source/extensions/filters/http/match_delegate/config.cc +++ b/source/extensions/filters/http/match_delegate/config.cc @@ -121,7 +121,7 @@ void DelegatingStreamFilter::FilterMatchState::evaluateMatchTree( if (match_tree_evaluated_ && match_result.result_) { const auto result = match_result.result_(); - if (SkipAction().typeUrl() == result->typeUrl()) { + if ((result == nullptr) || (SkipAction().typeUrl() == result->typeUrl())) { skip_filter_ = true; } else { ASSERT(base_filter_ != nullptr); diff --git a/test/extensions/filters/http/composite/composite_filter_integration_test.cc b/test/extensions/filters/http/composite/composite_filter_integration_test.cc index 709ee5da947c..cb670099de52 100644 --- a/test/extensions/filters/http/composite/composite_filter_integration_test.cc +++ b/test/extensions/filters/http/composite/composite_filter_integration_test.cc @@ -178,7 +178,12 @@ class CompositeFilterIntegrationTest : public testing::TestWithParammakeRequestWithBody(default_request_headers_, 1024); waitForNextUpstreamRequest(); @@ -379,12 +390,53 @@ TEST_P(CompositeFilterIntegrationTest, TestBasicDynamicFilter) { } { + // Sending headers matching the xDS config, sampled and the action filter is + // executed. Local reply with status code 403 is sent back. auto response = codec_client_->makeRequestWithBody(match_request_headers_, 1024); ASSERT_TRUE(response->waitForEndStream()); EXPECT_THAT(response->headers(), Http::HttpStatusIs("403")); } } +// Verifies that with dynamic config, if not sampled, then the action filter is skipped. +TEST_P(CompositeFilterIntegrationTest, TestBasicDynamicFilterNoSampling) { + prependCompositeDynamicFilter("composite-dynamic", "set_response_code.yaml", false); + initialize(); + if (downstream_filter_) { + test_server_->waitForCounterGe( + "extension_config_discovery.http_filter.set-response-code.config_reload", 1); + } else { + test_server_->waitForCounterGe( + "extension_config_discovery.upstream_http_filter.set-response-code.config_reload", 1); + } + test_server_->waitUntilListenersReady(); + test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + + codec_client_ = makeHttpConnection(lookupPort("http")); + { + // Sending default headers, not matching the xDS config, the request reaches backend, + // 200 is sent back. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); + waitForNextUpstreamRequest(); + + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_THAT(response->headers(), Http::HttpStatusIs("200")); + } + + { + // Sending headers matching the xDS config, not sampled. The action filter is + // skipped. The request reaches backend, and 200 is sent back. + // 200 is returned back. + auto response = codec_client_->makeRequestWithBody(match_request_headers_, 1024); + waitForNextUpstreamRequest(); + + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_THAT(response->headers(), Http::HttpStatusIs("200")); + } +} + // Verifies that if ECDS response is not sent, the missing filter config is applied that returns // 500. TEST_P(CompositeFilterIntegrationTest, TestMissingDynamicFilter) { @@ -531,46 +583,46 @@ class CompositeFilterSeverContextIntegrationTest cleanupUpstreamAndDownstream(); } - void initializeConfig(bool downstream_filter, bool downstream) { - config_helper_.addConfigModifier( - [downstream_filter, downstream, this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { - // Ensure "HTTP2 with no prior knowledge." Necessary for gRPC and for headers - ConfigHelper::setHttp2( - *(bootstrap.mutable_static_resources()->mutable_clusters()->Mutable(0))); - - // Loading filter config from YAML for cluster_0. - prependServerFactoryContextFilter(downstream_filter, downstream); - // Clusters for test gRPC servers. The HTTP filters is not needed for these clusters. - for (size_t i = 0; i < grpc_upstreams_.size(); ++i) { - auto* server_cluster = bootstrap.mutable_static_resources()->add_clusters(); - std::string cluster_name = absl::StrCat("test_server_", i); - server_cluster->set_name(cluster_name); - server_cluster->mutable_load_assignment()->set_cluster_name(cluster_name); - auto* address = server_cluster->mutable_load_assignment() - ->add_endpoints() - ->add_lb_endpoints() - ->mutable_endpoint() - ->mutable_address() - ->mutable_socket_address(); - address->set_address(Network::Test::getLoopbackAddressString(ipVersion())); - envoy::extensions::upstreams::http::v3::HttpProtocolOptions protocol_options; - protocol_options.mutable_explicit_http_config()->mutable_http2_protocol_options(); - (*server_cluster->mutable_typed_extension_protocol_options()) - ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] - .PackFrom(protocol_options); - } - // Parameterize with defer processing to prevent bit rot as filter made - // assumptions of data flow, prior relying on eager processing. - config_helper_.addRuntimeOverride(Runtime::defer_processing_backedup_streams, - deferredProcessing() ? "true" : "false"); - }); + void initializeConfig(bool downstream_filter, bool downstream, int sample_percent) { + config_helper_.addConfigModifier([downstream_filter, downstream, sample_percent, + this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // Ensure "HTTP2 with no prior knowledge." Necessary for gRPC and for headers + ConfigHelper::setHttp2( + *(bootstrap.mutable_static_resources()->mutable_clusters()->Mutable(0))); + + // Loading filter config from YAML for cluster_0. + prependServerFactoryContextFilter(downstream_filter, downstream, sample_percent); + // Clusters for test gRPC servers. The HTTP filters is not needed for these clusters. + for (size_t i = 0; i < grpc_upstreams_.size(); ++i) { + auto* server_cluster = bootstrap.mutable_static_resources()->add_clusters(); + std::string cluster_name = absl::StrCat("test_server_", i); + server_cluster->set_name(cluster_name); + server_cluster->mutable_load_assignment()->set_cluster_name(cluster_name); + auto* address = server_cluster->mutable_load_assignment() + ->add_endpoints() + ->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + address->set_address(Network::Test::getLoopbackAddressString(ipVersion())); + envoy::extensions::upstreams::http::v3::HttpProtocolOptions protocol_options; + protocol_options.mutable_explicit_http_config()->mutable_http2_protocol_options(); + (*server_cluster->mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(protocol_options); + } + // Parameterize with defer processing to prevent bit rot as filter made + // assumptions of data flow, prior relying on eager processing. + config_helper_.addRuntimeOverride(Runtime::defer_processing_backedup_streams, + deferredProcessing() ? "true" : "false"); + }); setUpstreamProtocol(Http::CodecType::HTTP2); setDownstreamProtocol(Http::CodecType::HTTP2); } void - prependServerFactoryContextFilter(bool downstream_filter, bool downstream, + prependServerFactoryContextFilter(bool downstream_filter, bool downstream, int sample_percent, const std::string& name = "envoy.filters.http.match_delegate") { std::string context_filter_config = "ServerFactoryContextFilterConfig"; if (!downstream_filter) { @@ -620,6 +672,10 @@ class CompositeFilterSeverContextIntegrationTest name: composite-action typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.composite.v3.ExecuteFilterAction + sample_percent: + default_value: + numerator: %d + denominator: HUNDRED typed_config: name: server-factory-context-filter typed_config: @@ -627,11 +683,12 @@ class CompositeFilterSeverContextIntegrationTest grpc_service: %s )EOF", - name, context_filter_config, grpc_config), + name, sample_percent, context_filter_config, + grpc_config), downstream); } - void serverContextBasicFlowTest() { + void serverContextBasicFlowTest(int sample_percent) { HttpIntegrationTest::initialize(); auto conn = makeClientConnection(lookupPort("http")); @@ -644,20 +701,23 @@ class CompositeFilterSeverContextIntegrationTest // Send request from downstream to upstream. auto response = codec_client_->makeHeaderOnlyRequest(request_headers); - // Wait for side stream request. - helloworld::HelloRequest request; - request.set_name("hello"); - ASSERT_TRUE(grpc_upstreams_[0]->waitForHttpConnection(*dispatcher_, connection_)); - ASSERT_TRUE(connection_->waitForNewStream(*dispatcher_, stream_)); - ASSERT_TRUE(stream_->waitForGrpcMessage(*dispatcher_, request)); - - // Start the grpc side stream. - stream_->startGrpcStream(); - - // Send the side stream response. - helloworld::HelloReply reply; - reply.set_message("ack"); - stream_->sendGrpcMessage(reply); + // Send request to side stream server when all sampled, i.e, sample_percent = 100. + if (sample_percent == 100) { + // Wait for side stream request. + helloworld::HelloRequest request; + request.set_name("hello"); + ASSERT_TRUE(grpc_upstreams_[0]->waitForHttpConnection(*dispatcher_, connection_)); + ASSERT_TRUE(connection_->waitForNewStream(*dispatcher_, stream_)); + ASSERT_TRUE(stream_->waitForGrpcMessage(*dispatcher_, request)); + + // Start the grpc side stream. + stream_->startGrpcStream(); + + // Send the side stream response. + helloworld::HelloReply reply; + reply.set_message("ack"); + stream_->sendGrpcMessage(reply); + } // Handle the upstream request. ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); @@ -666,8 +726,10 @@ class CompositeFilterSeverContextIntegrationTest upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); upstream_request_->encodeData(100, true); - // Close the grpc stream. - stream_->finishGrpcStream(Grpc::Status::Ok); + if (sample_percent == 100) { + // Close the grpc stream. + stream_->finishGrpcStream(Grpc::Status::Ok); + } // Verify the response from upstream to downstream. ASSERT_TRUE(response->waitForEndStream()); @@ -685,19 +747,36 @@ INSTANTIATE_TEST_SUITE_P( GRPC_CLIENT_INTEGRATION_DEFERRED_PROCESSING_PARAMS, Grpc::GrpcClientIntegrationParamTestWithDeferredProcessing::protocolTestParamsToString); -TEST_P(CompositeFilterSeverContextIntegrationTest, BasicFlowDownstreamFilterInDownstream) { - initializeConfig(/*downstream_filter*/ true, /*downstream*/ true); - serverContextBasicFlowTest(); +TEST_P(CompositeFilterSeverContextIntegrationTest, + BasicFlowDownstreamFilterInDownstreamAllSampled) { + initializeConfig(/*downstream_filter*/ true, /*downstream*/ true, /*sample_percent*/ 100); + serverContextBasicFlowTest(100); +} + +TEST_P(CompositeFilterSeverContextIntegrationTest, BasicFlowDualFilterInDownstreamAllSampled) { + initializeConfig(/*downstream_filter*/ false, /*downstream*/ true, /*sample_percent*/ 100); + serverContextBasicFlowTest(100); +} + +TEST_P(CompositeFilterSeverContextIntegrationTest, BasicFlowDualFilterInUpstreamAllSampled) { + initializeConfig(/*downstream_filter*/ false, /*downstream*/ false, /*sample_percent*/ 100); + serverContextBasicFlowTest(100); +} + +TEST_P(CompositeFilterSeverContextIntegrationTest, + BasicFlowDownstreamFilterInDownstreamNoneSampled) { + initializeConfig(/*downstream_filter*/ true, /*downstream*/ true, /*sample_percent*/ 0); + serverContextBasicFlowTest(0); } -TEST_P(CompositeFilterSeverContextIntegrationTest, BasicFlowDualFilterInDownstream) { - initializeConfig(/*downstream_filter*/ false, /*downstream*/ true); - serverContextBasicFlowTest(); +TEST_P(CompositeFilterSeverContextIntegrationTest, BasicFlowDualFilterInDownstreamNoneSampled) { + initializeConfig(/*downstream_filter*/ false, /*downstream*/ true, /*sample_percent*/ 0); + serverContextBasicFlowTest(0); } -TEST_P(CompositeFilterSeverContextIntegrationTest, BasicFlowDualFilterInUpstream) { - initializeConfig(/*downstream_filter*/ false, /*downstream*/ false); - serverContextBasicFlowTest(); +TEST_P(CompositeFilterSeverContextIntegrationTest, BasicFlowDualFilterInUpstreamNoneSampled) { + initializeConfig(/*downstream_filter*/ false, /*downstream*/ false, /*sample_percent*/ 0); + serverContextBasicFlowTest(0); } } // namespace diff --git a/test/extensions/filters/http/composite/filter_test.cc b/test/extensions/filters/http/composite/filter_test.cc index 7ba33f4054a1..d37bd319eae9 100644 --- a/test/extensions/filters/http/composite/filter_test.cc +++ b/test/extensions/filters/http/composite/filter_test.cc @@ -515,6 +515,75 @@ TEST(ConfigTest, TestDownstreamFilterNoOverridingServerContext) { EnvoyException, "Creating filter factory from server factory context is not supported"); } +// Config test to check if sample_percent config is not specified. +TEST(ConfigTest, TestSamplePercentNotSpecifiedl) { + const std::string yaml_string = R"EOF( + typed_config: + name: set-response-code + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault + abort: + http_status: 503 + )EOF"; + + testing::NiceMock server_factory_context; + NiceMock& runtime = server_factory_context.runtime_loader_; + envoy::extensions::filters::http::composite::v3::ExecuteFilterAction config; + TestUtility::loadFromYaml(yaml_string, config); + ExecuteFilterActionFactory factory; + EXPECT_TRUE(factory.isSampled(config, runtime)); +} + +// Config test to check if sample_percent config is in place and feature enabled. +TEST(ConfigTest, TestSamplePercentInPlaceFeatureEnabled) { + const std::string yaml_string = R"EOF( + typed_config: + name: set-response-code + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault + abort: + http_status: 503 + sample_percent: + default_value: + numerator: 30 + denominator: HUNDRED + )EOF"; + + testing::NiceMock server_factory_context; + NiceMock& runtime = server_factory_context.runtime_loader_; + envoy::extensions::filters::http::composite::v3::ExecuteFilterAction config; + TestUtility::loadFromYaml(yaml_string, config); + ExecuteFilterActionFactory factory; + EXPECT_CALL(runtime.snapshot_, + featureEnabled(_, testing::A())) + .WillOnce(testing::Return(true)); + EXPECT_TRUE(factory.isSampled(config, runtime)); +} + +// Config test to check if sample_percent config is in place and feature not enabled. +TEST(ConfigTest, TestSamplePercentInPlaceFeatureNotEnabled) { + const std::string yaml_string = R"EOF( + typed_config: + name: set-response-code + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault + abort: + http_status: 503 + sample_percent: + runtime_key: + )EOF"; + + testing::NiceMock server_factory_context; + NiceMock& runtime = server_factory_context.runtime_loader_; + envoy::extensions::filters::http::composite::v3::ExecuteFilterAction config; + TestUtility::loadFromYaml(yaml_string, config); + ExecuteFilterActionFactory factory; + EXPECT_CALL(runtime.snapshot_, + featureEnabled(_, testing::A())) + .WillOnce(testing::Return(false)); + EXPECT_FALSE(factory.isSampled(config, runtime)); +} + TEST_F(FilterTest, FilterStateShouldBeUpdatedWithTheMatchingActionForDynamicConfig) { const std::string yaml_string = R"EOF( dynamic_config: From e1c6235236a2392d3d44c22857d035f0b1060c85 Mon Sep 17 00:00:00 2001 From: code Date: Thu, 27 Jun 2024 23:58:48 +0800 Subject: [PATCH 06/31] random: changes the random() and uuid() to static methods (#34919) * random: changes the random() and uuid() to static methods Signed-off-by: wbpcode * fix test because invalid length Signed-off-by: wbpcode --------- Signed-off-by: wbpcode Co-authored-by: wbpcode --- source/common/common/random_generator.cc | 14 +++++++++----- source/common/common/random_generator.h | 12 ++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/source/common/common/random_generator.cc b/source/common/common/random_generator.cc index ba00e970f843..cf6a8e9a92c2 100644 --- a/source/common/common/random_generator.cc +++ b/source/common/common/random_generator.cc @@ -7,9 +7,10 @@ namespace Envoy { namespace Random { -const size_t RandomGeneratorImpl::UUID_LENGTH = 36; +constexpr size_t CONSTEXPR_UUID_LENGTH = 36; +const size_t RandomGeneratorImpl::UUID_LENGTH = CONSTEXPR_UUID_LENGTH; -uint64_t RandomGeneratorImpl::random() { +uint64_t RandomUtility::random() { // Prefetch 256 * sizeof(uint64_t) bytes of randomness. buffered_idx is initialized to 256, // i.e. out-of-range value, so the buffer will be filled with randomness on the first call // to this function. @@ -46,7 +47,7 @@ uint64_t RandomGeneratorImpl::random() { return buffered[buffered_idx++]; } -std::string RandomGeneratorImpl::uuid() { +std::string RandomUtility::uuid() { // Prefetch 2048 bytes of randomness. buffered_idx is initialized to sizeof(buffered), // i.e. out-of-range value, so the buffer will be filled with randomness on the first // call to this function. @@ -88,7 +89,7 @@ std::string RandomGeneratorImpl::uuid() { // Convert UUID to a string representation, e.g. a121e9e1-feae-4136-9e0e-6fac343d56c9. static const char* const hex = "0123456789abcdef"; - char uuid[UUID_LENGTH]; + char uuid[CONSTEXPR_UUID_LENGTH]; for (uint8_t i = 0; i < 4; i++) { const uint8_t d = rand[i]; @@ -128,8 +129,11 @@ std::string RandomGeneratorImpl::uuid() { uuid[2 * i + 5] = hex[d & 0x0f]; } - return {uuid, UUID_LENGTH}; + return {uuid, CONSTEXPR_UUID_LENGTH}; } +uint64_t RandomGeneratorImpl::random() { return RandomUtility::random(); } +std::string RandomGeneratorImpl::uuid() { return RandomUtility::uuid(); } + } // namespace Random } // namespace Envoy diff --git a/source/common/common/random_generator.h b/source/common/common/random_generator.h index 47c5da6b1f9d..640e964b90af 100644 --- a/source/common/common/random_generator.h +++ b/source/common/common/random_generator.h @@ -4,6 +4,18 @@ namespace Envoy { namespace Random { + +/** + * Utility class for generating random numbers and UUIDs. These methods in class are thread-safe. + * NOTE: The RandomGenerator from FactoryContext should be used as priority. Only use this class + * when RandomGenerator is not available and unit test mocking is not needed. + */ +class RandomUtility { +public: + static uint64_t random(); + static std::string uuid(); +}; + /** * Implementation of RandomGenerator that uses per-thread RANLUX generators seeded with current * time. From f3ee7c778bce18db1862774d70cd36e019344847 Mon Sep 17 00:00:00 2001 From: Joe Kralicky Date: Thu, 27 Jun 2024 12:29:52 -0400 Subject: [PATCH 07/31] tls: fix duplicated ':TLS_error_end' suffix in socket failure reason (#34799) This fix also prevents duplicated debug logs, since they are always generated after the end suffix is appended to the failure reason. Signed-off-by: Joe Kralicky --- source/common/tls/ssl_socket.cc | 6 +++++- .../tls/integration/ssl_integration_test.cc | 21 +++++++++---------- .../tcp_grpc_access_log_integration_test.cc | 6 +++--- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/source/common/tls/ssl_socket.cc b/source/common/tls/ssl_socket.cc index 2fdfcc147d6d..e731640d680e 100644 --- a/source/common/tls/ssl_socket.cc +++ b/source/common/tls/ssl_socket.cc @@ -238,6 +238,10 @@ void SslSocket::drainErrorQueue() { absl::NullSafeStringView(ERR_reason_error_string(err))); } + if (!saw_error) { + return; + } + if (!failure_reason_.empty()) { if (new_ssl_failure_format) { absl::StrAppend(&failure_reason_, ":TLS_error_end"); @@ -247,7 +251,7 @@ void SslSocket::drainErrorQueue() { failure_reason_); } - if (saw_error && !saw_counted_error) { + if (!saw_counted_error) { ctx_->stats().connection_error_.inc(); } } diff --git a/test/common/tls/integration/ssl_integration_test.cc b/test/common/tls/integration/ssl_integration_test.cc index 17086b049b3c..fd2e6e844381 100644 --- a/test/common/tls/integration/ssl_integration_test.cc +++ b/test/common/tls/integration/ssl_integration_test.cc @@ -765,13 +765,13 @@ TEST_P(SslCertficateIntegrationTest, ServerEcdsaClientRsaOnlyWithAccessLog) { auto log_result = waitForAccessLog(listener_access_log_name_); if (tls_version_ == envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3) { - EXPECT_THAT(log_result, - StartsWith("DOWNSTREAM_TRANSPORT_FAILURE_REASON=TLS_error:|268435709:SSL_routines:" - "OPENSSL_internal:NO_COMMON_SIGNATURE_ALGORITHMS:TLS_error_end")); + EXPECT_EQ(log_result, + "DOWNSTREAM_TRANSPORT_FAILURE_REASON=TLS_error:|268435709:SSL_routines:" + "OPENSSL_internal:NO_COMMON_SIGNATURE_ALGORITHMS:TLS_error_end FILTER_CHAIN_NAME=-"); } else { - EXPECT_THAT(log_result, - StartsWith("DOWNSTREAM_TRANSPORT_FAILURE_REASON=TLS_error:|268435640:" - "SSL_routines:OPENSSL_internal:NO_SHARED_CIPHER:TLS_error_end")); + EXPECT_EQ(log_result, + "DOWNSTREAM_TRANSPORT_FAILURE_REASON=TLS_error:|268435640:" + "SSL_routines:OPENSSL_internal:NO_SHARED_CIPHER:TLS_error_end FILTER_CHAIN_NAME=-"); } } @@ -791,12 +791,11 @@ TEST_P(SslCertficateIntegrationTest, ServerEcdsaClientRsaOnlyWithAccessLogOrigin auto log_result = waitForAccessLog(listener_access_log_name_); if (tls_version_ == envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3) { - EXPECT_THAT(log_result, - StartsWith("DOWNSTREAM_TRANSPORT_FAILURE_REASON=TLS_error:_268435709:SSL_routines:" - "OPENSSL_internal:NO_COMMON_SIGNATURE_ALGORITHMS")); + EXPECT_EQ(log_result, "DOWNSTREAM_TRANSPORT_FAILURE_REASON=TLS_error:_268435709:SSL_routines:" + "OPENSSL_internal:NO_COMMON_SIGNATURE_ALGORITHMS FILTER_CHAIN_NAME=-"); } else { - EXPECT_THAT(log_result, StartsWith("DOWNSTREAM_TRANSPORT_FAILURE_REASON=TLS_error:_268435640:" - "SSL_routines:OPENSSL_internal:NO_SHARED_CIPHER")); + EXPECT_EQ(log_result, "DOWNSTREAM_TRANSPORT_FAILURE_REASON=TLS_error:_268435640:" + "SSL_routines:OPENSSL_internal:NO_SHARED_CIPHER FILTER_CHAIN_NAME=-"); } } diff --git a/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc index b463ecd122a6..1ff1ad08d217 100644 --- a/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc +++ b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc @@ -833,7 +833,7 @@ TEST_P(TcpGrpcAccessLogIntegrationTest, TlsHandshakeFailure_VerifyFailed) { downstream_local_address: socket_address: address: {0} - downstream_transport_failure_reason: "TLS_error:|268435581:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED:TLS_error_end:TLS_error_end" + downstream_transport_failure_reason: "TLS_error:|268435581:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED:TLS_error_end" access_log_type: NotSet downstream_direct_remote_address: socket_address: @@ -895,7 +895,7 @@ TEST_P(TcpGrpcAccessLogIntegrationTest, TlsHandshakeFailure_NoSharedCipher) { downstream_local_address: socket_address: address: {0} - downstream_transport_failure_reason: "TLS_error:|268435640:SSL routines:OPENSSL_internal:NO_SHARED_CIPHER:TLS_error_end:TLS_error_end" + downstream_transport_failure_reason: "TLS_error:|268435640:SSL routines:OPENSSL_internal:NO_SHARED_CIPHER:TLS_error_end" access_log_type: NotSet downstream_direct_remote_address: socket_address: @@ -952,7 +952,7 @@ TEST_P(TcpGrpcAccessLogIntegrationTest, SslHandshakeFailure_UnsupportedProtocol) downstream_local_address: socket_address: address: {0} - downstream_transport_failure_reason: "TLS_error:|268435696:SSL routines:OPENSSL_internal:UNSUPPORTED_PROTOCOL:TLS_error_end:TLS_error_end" + downstream_transport_failure_reason: "TLS_error:|268435696:SSL routines:OPENSSL_internal:UNSUPPORTED_PROTOCOL:TLS_error_end" access_log_type: NotSet downstream_direct_remote_address: socket_address: From cb515cbd0e8dd184d948409d4e3842c647cd9ab2 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 27 Jun 2024 13:27:59 -0400 Subject: [PATCH 08/31] coverage: ratcheting (#34894) Signed-off-by: Alyssa Wilk --- test/per_file_coverage.sh | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 3685a28f0e2f..a387bfb6e6af 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -6,22 +6,22 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common:96.2" "source/common/api:84.5" # flaky due to posix: be careful adjusting "source/common/api/posix:83.8" # flaky (accept failover non-deterministic): be careful adjusting -"source/common/config:95.6" +"source/common/config:95.8" "source/common/crypto:95.5" "source/common/event:95.1" # Emulated edge events guards don't report LCOV -"source/common/filesystem/posix:96.2" # FileReadToEndNotReadable fails in some env; createPath can't test all failure branches. +"source/common/filesystem/posix:96.3" # FileReadToEndNotReadable fails in some env; createPath can't test all failure branches. "source/common/http/http2:95.9" -"source/common/json:94.6" +"source/common/json:94.8" "source/common/matcher:94.4" -"source/common/memory:73.6" # tcmalloc code path is not enabled in coverage build, only gperf tcmalloc, see PR#32589 +"source/common/memory:74.5" # tcmalloc code path is not enabled in coverage build, only gperf tcmalloc, see PR#32589 "source/common/network:94.4" # Flaky, `activateFileEvents`, `startSecureTransport` and `ioctl`, listener_socket do not always report LCOV "source/common/network/dns_resolver:91.4" # A few lines of MacOS code not tested in linux scripts. Tested in MacOS scripts -"source/common/quic:93.6" +"source/common/quic:93.7" "source/common/secret:95.4" "source/common/signal:87.2" # Death tests don't report LCOV "source/common/thread:0.0" # Death tests don't report LCOV "source/common/watchdog:58.6" # Death tests don't report LCOV -"source/exe:94.0" # increased by #32346, need coverage for terminate_handler and hot restart failures +"source/exe:94.2" # increased by #32346, need coverage for terminate_handler and hot restart failures "source/extensions/clusters/common:95.6" "source/extensions/common:93.0" #flaky: be careful adjusting "source/extensions/common/proxy_protocol:93.8" # Adjusted for security patch @@ -29,25 +29,25 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/common/wasm:88.0" # flaky: be careful adjusting "source/extensions/common/wasm/ext:92.0" "source/extensions/filters/common/fault:94.5" -"source/extensions/filters/common/rbac:90.7" +"source/extensions/filters/common/rbac:90.8" "source/extensions/filters/http/cache:95.1" "source/extensions/filters/http/grpc_json_transcoder:93.8" # TODO(#28232) -"source/extensions/filters/http/ip_tagging:88.0" +"source/extensions/filters/http/ip_tagging:88.2" "source/extensions/filters/http/kill_request:91.7" # Death tests don't report LCOV "source/extensions/filters/http/wasm:1.3" # Disabled due to issue (#24164) "source/extensions/filters/listener/original_src:92.1" "source/extensions/filters/network/mongo_proxy:96.1" "source/extensions/filters/network/sni_cluster:88.9" -"source/extensions/filters/network/wasm:76.9" +"source/extensions/filters/network/wasm:77.8" "source/extensions/http/cache/simple_http_cache:95.9" "source/extensions/rate_limit_descriptors:95.0" "source/extensions/rate_limit_descriptors/expr:95.0" "source/extensions/stat_sinks/graphite_statsd:82.8" # Death tests don't report LCOV "source/extensions/stat_sinks/statsd:85.2" # Death tests don't report LCOV "source/extensions/tracers:96.5" -"source/extensions/tracers/common:74.8" -"source/extensions/tracers/common/ot:72.9" -"source/extensions/tracers/opencensus:93.9" +"source/extensions/tracers/common:75.0" +"source/extensions/tracers/common/ot:73.1" +"source/extensions/tracers/opencensus:94.0" "source/extensions/tracers/zipkin:95.8" "source/extensions/transport_sockets:97.4" "source/common/tls:94.9" @@ -60,7 +60,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/listener_managers/validation_listener_manager:70.5" "source/extensions/watchdog/profile_action:83.3" "source/server:91.0" # flaky: be careful adjusting. See https://github.com/envoyproxy/envoy/issues/15239 -"source/server/config_validation:91.3" +"source/server/config_validation:91.4" "source/extensions/health_checkers:96.1" "source/extensions/health_checkers/http:93.9" "source/extensions/health_checkers/grpc:92.1" From c2135898f8f7bb138295ad19e084e8f80702a232 Mon Sep 17 00:00:00 2001 From: Rama Chavali Date: Fri, 28 Jun 2024 00:36:14 +0530 Subject: [PATCH 09/31] http: fix cookie attributes (#34885) --------- Signed-off-by: Rama Chavali --- changelogs/current.yaml | 4 ++ source/common/http/hash_policy.cc | 14 +++++-- .../multiplexed_integration_test.cc | 39 +++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index d1f51c655210..3b6321789f77 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -218,6 +218,10 @@ bug_fixes: change: | Fixed missing :ref:`additional addresses ` for :ref:`LbEndpoint ` in config dump. +- area: http + change: | + Fixed a bug where additional :ref:`cookie attributes ` + are not sent properly to clients. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/http/hash_policy.cc b/source/common/http/hash_policy.cc index 3d8094147a59..046d895dfc30 100644 --- a/source/common/http/hash_policy.cc +++ b/source/common/http/hash_policy.cc @@ -82,7 +82,11 @@ class CookieHashMethod : public HashMethodImplBase { CookieHashMethod(const std::string& key, const std::string& path, const absl::optional& ttl, bool terminal, const CookieAttributeRefVector attributes) - : HashMethodImplBase(terminal), key_(key), path_(path), ttl_(ttl), attributes_(attributes) {} + : HashMethodImplBase(terminal), key_(key), path_(path), ttl_(ttl) { + for (const auto& attribute : attributes) { + attributes_.push_back(attribute); + } + } absl::optional evaluate(const Network::Address::Instance*, const RequestHeaderMap& headers, @@ -91,7 +95,11 @@ class CookieHashMethod : public HashMethodImplBase { absl::optional hash; std::string value = Utility::parseCookieValue(headers, key_); if (value.empty() && ttl_.has_value()) { - value = add_cookie(key_, path_, ttl_.value(), attributes_); + CookieAttributeRefVector attributes; + for (const auto& attribute : attributes_) { + attributes.push_back(attribute); + } + value = add_cookie(key_, path_, ttl_.value(), attributes); hash = HashUtil::xxHash64(value); } else if (!value.empty()) { @@ -104,7 +112,7 @@ class CookieHashMethod : public HashMethodImplBase { const std::string key_; const std::string path_; const absl::optional ttl_; - const CookieAttributeRefVector attributes_; + std::vector attributes_; }; class IpHashMethod : public HashMethodImplBase { diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index a11beff41089..39882e910152 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -1841,6 +1841,45 @@ TEST_P(MultiplexedRingHashIntegrationTest, CookieRoutingNoCookieWithNonzeroTtlSe EXPECT_EQ(set_cookies.size(), 1); } +TEST_P(MultiplexedRingHashIntegrationTest, + CookieRoutingNoCookieWithNonzeroTtlSetAndWithAttributes) { + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { + auto* hash_policy = hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->add_hash_policy(); + auto* cookie = hash_policy->mutable_cookie(); + cookie->set_name("foo"); + cookie->mutable_ttl()->set_seconds(15); + auto* attribute_1 = cookie->mutable_attributes()->Add(); + attribute_1->set_name("test1"); + attribute_1->set_value("value1"); + auto* attribute_2 = cookie->mutable_attributes()->Add(); + attribute_2->set_name("test2"); + attribute_2->set_value("value2"); + }); + + std::set set_cookies; + sendMultipleRequests( + 1024, + Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}, + [&](IntegrationStreamDecoder& response) { + EXPECT_EQ("200", response.headers().getStatusValue()); + std::string value( + response.headers().get(Http::Headers::get().SetCookie)[0]->value().getStringView()); + set_cookies.insert(value); + EXPECT_THAT(value, + MatchesRegex("foo=.*; Max-Age=15; test1=value1; test2=value2; HttpOnly")); + }); + EXPECT_EQ(set_cookies.size(), 1); +} + TEST_P(MultiplexedRingHashIntegrationTest, CookieRoutingNoCookieWithZeroTtlSet) { config_helper_.addConfigModifier( [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& From cb91c466bfb496348a81dbee40fc454512c37b23 Mon Sep 17 00:00:00 2001 From: Jacob Bohanon Date: Thu, 27 Jun 2024 15:12:46 -0400 Subject: [PATCH 10/31] ext_proc: Improve test coverage in matching_utils (#34947) Signed-off-by: Jacob Bohanon --- .../filters/http/ext_proc/matching_utils.cc | 27 +++++- test/extensions/filters/http/ext_proc/BUILD | 22 +++++ .../ext_proc/ext_proc_integration_test.cc | 12 ++- .../http/ext_proc/matching_utils_test.cc | 87 +++++++++++++++++++ 4 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 test/extensions/filters/http/ext_proc/matching_utils_test.cc diff --git a/source/extensions/filters/http/ext_proc/matching_utils.cc b/source/extensions/filters/http/ext_proc/matching_utils.cc index 327ccdf2dd3d..35ffaf9ced5d 100644 --- a/source/extensions/filters/http/ext_proc/matching_utils.cc +++ b/source/extensions/filters/http/ext_proc/matching_utils.cc @@ -63,16 +63,35 @@ ExpressionManager::evaluateAttributes(const Filters::Common::Expr::Activation& a continue; } + // Possible types from source/extensions/filters/common/expr/context.cc: + // - String + // - StringView + // - Map + // - Timestamp + // - Int64 + // - Duration + // - Bool + // - Uint64 + // - Bytes + // + // Of these, we need to handle + // - bool as bool + // - int64, uint64 as number + // - everything else as string via print + // + // Handling all value types here would be graceful but is not currently + // testable and drives down coverage %. This is not a _great_ reason to + // not do it; will get feedback from reviewers. ProtobufWkt::Value value; switch (result.value().type()) { case google::api::expr::runtime::CelValue::Type::kBool: value.set_bool_value(result.value().BoolOrDie()); break; - case google::api::expr::runtime::CelValue::Type::kNullType: - value.set_null_value(ProtobufWkt::NullValue{}); + case google::api::expr::runtime::CelValue::Type::kInt64: + value.set_number_value(result.value().Int64OrDie()); break; - case google::api::expr::runtime::CelValue::Type::kDouble: - value.set_number_value(result.value().DoubleOrDie()); + case google::api::expr::runtime::CelValue::Type::kUint64: + value.set_number_value(result.value().Uint64OrDie()); break; default: value.set_string_value(Filters::Common::Expr::print(result.value())); diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 4f3a82ff7f01..58e3807dd565 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -110,6 +110,28 @@ envoy_extension_cc_test( ], ) +envoy_extension_cc_test( + name = "matching_utils_test", + size = "small", + srcs = ["matching_utils_test.cc"], + copts = select({ + "//bazel:windows_x86_64": [], + "//conditions:default": [ + "-DUSE_CEL_PARSER", + ], + }), + extension_names = ["envoy.filters.http.ext_proc"], + tags = ["skip_on_windows"], + deps = [ + ":utils_lib", + "//source/extensions/filters/http/ext_proc:matching_utils_lib", + "//test/mocks/local_info:local_info_mocks", + "//test/mocks/server:server_factory_context_mocks", + "//test/mocks/stream_info:stream_info_mocks", + "//test/test_common:utility_lib", + ], +) + envoy_extension_cc_test( name = "mutation_utils_test", size = "small", diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index df31b44dd1b5..a7d567388699 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -3830,9 +3830,11 @@ TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { proto_config_.mutable_request_attributes()->Add("request.path"); proto_config_.mutable_request_attributes()->Add("request.method"); proto_config_.mutable_request_attributes()->Add("request.scheme"); - proto_config_.mutable_request_attributes()->Add("connection.mtls"); + proto_config_.mutable_request_attributes()->Add("request.size"); // tests int64 + proto_config_.mutable_request_attributes()->Add("connection.mtls"); // tests bool + proto_config_.mutable_request_attributes()->Add("connection.id"); // tests uint64 proto_config_.mutable_request_attributes()->Add("response.code"); - proto_config_.mutable_response_attributes()->Add("response.code"); + proto_config_.mutable_response_attributes()->Add("response.code"); // tests int64 proto_config_.mutable_response_attributes()->Add("response.code_details"); initializeConfig(); @@ -3852,9 +3854,11 @@ TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { EXPECT_EQ(proto_struct.fields().at("request.path").string_value(), "/"); EXPECT_EQ(proto_struct.fields().at("request.method").string_value(), "GET"); EXPECT_EQ(proto_struct.fields().at("request.scheme").string_value(), "http"); + EXPECT_EQ(proto_struct.fields().at("request.size").number_value(), 0); EXPECT_EQ(proto_struct.fields().at("connection.mtls").bool_value(), false); + EXPECT_TRUE(proto_struct.fields().at("connection.id").has_number_value()); // Make sure we did not include the attribute which was not yet available. - EXPECT_EQ(proto_struct.fields().size(), 4); + EXPECT_EQ(proto_struct.fields().size(), 6); EXPECT_FALSE(proto_struct.fields().contains("response.code")); // Make sure we are not including any data in the deprecated HttpHeaders.attributes. @@ -3874,7 +3878,7 @@ TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { EXPECT_TRUE(req.has_response_headers()); EXPECT_EQ(req.attributes().size(), 1); auto proto_struct = req.attributes().at("envoy.filters.http.ext_proc"); - EXPECT_EQ(proto_struct.fields().at("response.code").string_value(), "200"); + EXPECT_EQ(proto_struct.fields().at("response.code").number_value(), 200); EXPECT_EQ(proto_struct.fields().at("response.code_details").string_value(), StreamInfo::ResponseCodeDetails::get().ViaUpstream); diff --git a/test/extensions/filters/http/ext_proc/matching_utils_test.cc b/test/extensions/filters/http/ext_proc/matching_utils_test.cc new file mode 100644 index 000000000000..32372a68ce09 --- /dev/null +++ b/test/extensions/filters/http/ext_proc/matching_utils_test.cc @@ -0,0 +1,87 @@ +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/filters/common/expr/evaluator.h" +#include "source/extensions/filters/http/ext_proc/matching_utils.h" + +#include "test/mocks/local_info/mocks.h" +#include "test/mocks/server/server_factory_context.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace ExternalProcessing { +namespace { + +using ::Envoy::Http::TestRequestHeaderMapImpl; +using ::Envoy::Http::TestRequestTrailerMapImpl; +using ::Envoy::Http::TestResponseHeaderMapImpl; +using ::Envoy::Http::TestResponseTrailerMapImpl; + +class ExpressionManagerTest : public testing::Test { +public: + void initialize() { builder_ = Envoy::Extensions::Filters::Common::Expr::getBuilder(context_); } + + std::shared_ptr builder_; + NiceMock context_; + testing::NiceMock stream_info_; + testing::NiceMock local_info_; + TestRequestHeaderMapImpl request_headers_; + TestResponseHeaderMapImpl response_headers_; + TestRequestTrailerMapImpl request_trailers_; + TestResponseTrailerMapImpl response_trailers_; + Protobuf::RepeatedPtrField req_matchers_; + Protobuf::RepeatedPtrField resp_matchers_; +}; + +#if defined(USE_CEL_PARSER) +TEST_F(ExpressionManagerTest, DuplicateAttributesIgnored) { + initialize(); + req_matchers_.Add("request.path"); + req_matchers_.Add("request.method"); + req_matchers_.Add("request.method"); + req_matchers_.Add("request.path"); + const auto expr_mgr = ExpressionManager(builder_, local_info_, req_matchers_, resp_matchers_); + + request_headers_.setMethod("GET"); + request_headers_.setPath("/foo"); + const auto activation_ptr = Filters::Common::Expr::createActivation( + &expr_mgr.localInfo(), stream_info_, &request_headers_, &response_headers_, + &response_trailers_); + + auto result = expr_mgr.evaluateRequestAttributes(*activation_ptr); + EXPECT_EQ(2, result.fields_size()); + EXPECT_NE(result.fields().end(), result.fields().find("request.path")); + EXPECT_NE(result.fields().end(), result.fields().find("request.method")); +} + +TEST_F(ExpressionManagerTest, UnparsableExpressionThrowsException) { + initialize(); + req_matchers_.Add("++"); + EXPECT_THROW_WITH_REGEX(ExpressionManager(builder_, local_info_, req_matchers_, resp_matchers_), + EnvoyException, "Unable to parse descriptor expression.*"); +} + +TEST_F(ExpressionManagerTest, EmptyExpressionReturnsEmptyStruct) { + initialize(); + const auto expr_mgr = ExpressionManager(builder_, local_info_, req_matchers_, resp_matchers_); + + request_headers_ = TestRequestHeaderMapImpl(); + request_headers_.setMethod("GET"); + request_headers_.setPath("/foo"); + const auto activation_ptr = Filters::Common::Expr::createActivation( + &expr_mgr.localInfo(), stream_info_, &request_headers_, &response_headers_, + &response_trailers_); + + EXPECT_EQ(0, expr_mgr.evaluateRequestAttributes(*activation_ptr).fields_size()); +} +#endif + +} // namespace +} // namespace ExternalProcessing +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy From dd3bdace1c3ce79d4e93e6742a53a25a6448e7b4 Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Thu, 27 Jun 2024 16:42:00 -0400 Subject: [PATCH 11/31] upstream: Add dynamic `LbPolicyData` to `Host`. (#34943) * Add dynamic `LbPolicyData` to `Host`. Signed-off-by: Misha Efimov * Kick CI Signed-off-by: Misha Efimov * Kick CI Signed-off-by: Misha Efimov --------- Signed-off-by: Misha Efimov --- envoy/upstream/upstream.h | 19 +++++++++++++++++++ source/common/upstream/upstream_impl.h | 6 ++++++ test/common/upstream/upstream_impl_test.cc | 17 +++++++++++++++++ test/mocks/upstream/host.h | 2 ++ 4 files changed, 44 insertions(+) diff --git a/envoy/upstream/upstream.h b/envoy/upstream/upstream.h index 45f82195f477..067bf303cfca 100644 --- a/envoy/upstream/upstream.h +++ b/envoy/upstream/upstream.h @@ -300,6 +300,25 @@ class Host : virtual public HostDescription { * Set true to disable active health check for the host. */ virtual void setDisableActiveHealthCheck(bool disable_active_health_check) PURE; + + /** + * Base interface for attaching LbPolicy-specific data to individual hosts. + */ + class HostLbPolicyData { + public: + virtual ~HostLbPolicyData() = default; + }; + using HostLbPolicyDataPtr = std::shared_ptr; + + /* Takes ownership of lb_policy_data and attaches it to the host. + * Must be called before the host is used across threads. + */ + virtual void setLbPolicyData(HostLbPolicyDataPtr lb_policy_data) PURE; + + /* + * @return a reference to the LbPolicyData attached to the host. + */ + virtual const HostLbPolicyDataPtr& lbPolicyData() const PURE; }; using HostConstSharedPtr = std::shared_ptr; diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 25a9d1158bfa..f3711b3e78dd 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -414,6 +414,11 @@ class HostImplBase : public Host, return std::make_unique(shared_from_this()); } + void setLbPolicyData(HostLbPolicyDataPtr lb_policy_data) override { + lb_policy_data_ = std::move(lb_policy_data); + } + const HostLbPolicyDataPtr& lbPolicyData() const override { return lb_policy_data_; } + protected: static CreateConnectionData createConnection(Event::Dispatcher& dispatcher, const ClusterInfo& cluster, @@ -437,6 +442,7 @@ class HostImplBase : public Host, // flag access? May be we could refactor HealthFlag to contain all these statuses and flags in the // future. std::atomic eds_health_status_{}; + HostLbPolicyDataPtr lb_policy_data_; struct HostHandleImpl : HostHandle { HostHandleImpl(const std::shared_ptr& parent) : parent_(parent) { diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index 512ccc57a035..f136279edfd0 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -1574,6 +1574,23 @@ TEST_F(HostImplTest, Weight) { EXPECT_EQ(std::numeric_limits::max(), host->weight()); } +TEST_F(HostImplTest, HostLbPolicyData) { + MockClusterMockPrioritySet cluster; + HostSharedPtr host = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), 1); + EXPECT_TRUE(host->lbPolicyData() == nullptr); + + class TestLbPolicyData : public Host::HostLbPolicyData { + public: + int foo = 42; + }; + + host->setLbPolicyData(std::make_shared()); + EXPECT_TRUE(host->lbPolicyData() != nullptr); + auto* test_policy_data = dynamic_cast(host->lbPolicyData().get()); + EXPECT_TRUE(test_policy_data != nullptr); + EXPECT_EQ(test_policy_data->foo, 42); +} + TEST_F(HostImplTest, HostnameCanaryAndLocality) { MockClusterMockPrioritySet cluster; envoy::config::core::v3::Metadata metadata; diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h index 5ab4f8b66688..e67f0a5ea6a5 100644 --- a/test/mocks/upstream/host.h +++ b/test/mocks/upstream/host.h @@ -207,6 +207,8 @@ class MockHostLight : public Host { MOCK_METHOD(void, priority, (uint32_t)); MOCK_METHOD(bool, warmed, (), (const)); MOCK_METHOD(absl::optional, lastHcPassTime, (), (const)); + MOCK_METHOD(void, setLbPolicyData, (HostLbPolicyDataPtr lb_policy_data)); + MOCK_METHOD(const HostLbPolicyDataPtr&, lbPolicyData, (), (const)); bool disable_active_health_check_ = false; }; From 8646385431c3b91b94a3727766813615eeeaca10 Mon Sep 17 00:00:00 2001 From: "Antonio V. Leonti" <53806445+antoniovleonti@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:32:00 -0400 Subject: [PATCH 12/31] add knob disable_dynamic_metadata_ingestion (#34691) * add knob disable_dynamic_metadata_ingestion Signed-off-by: antoniovleonti * move changelog entry to a more appropriate section Signed-off-by: antoniovleonti * ignore dynamic metadata rather than failing entire request Signed-off-by: antoniovleonti * fix proto documentation for field Signed-off-by: antoniovleonti * use BoolValue to get 'positive' bool name Signed-off-by: antoniovleonti * adjust wording of proto field comment Signed-off-by: antoniovleonti * add check that dynamic metadata is not being set Signed-off-by: antoniovleonti * fix doc link Signed-off-by: antoniovleonti * fix doc link Signed-off-by: antoniovleonti * fix doc link Signed-off-by: antoniovleonti * reword doc comment Signed-off-by: antoniovleonti --------- Signed-off-by: antoniovleonti Signed-off-by: Antonio V. Leonti <53806445+antoniovleonti@users.noreply.github.com> --- .../filters/http/ext_authz/v3/ext_authz.proto | 13 ++++++- changelogs/current.yaml | 6 +++ .../filters/http/ext_authz/ext_authz.cc | 32 ++++++++++------ .../filters/http/ext_authz/ext_authz.h | 6 ++- .../filters/http/ext_authz/ext_authz_test.cc | 38 +++++++++++++++++++ 5 files changed, 82 insertions(+), 13 deletions(-) diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index 986fa2c9166c..4b4e79ef1f9f 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -29,7 +29,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // External Authorization :ref:`configuration overview `. // [#extension: envoy.filters.http.ext_authz] -// [#next-free-field: 27] +// [#next-free-field: 28] message ExtAuthz { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ext_authz.v3.ExtAuthz"; @@ -279,6 +279,17 @@ message ExtAuthz { // correctness checks for all header / query parameter mutations (e.g. for invalid characters). // This field allows the filter to reject mutations to specific headers. config.common.mutation_rules.v3.HeaderMutationRules decoder_header_mutation_rules = 26; + + // Enable / disable ingestion of dynamic metadata from ext_authz service. + // + // If false, the filter will ignore dynamic metadata injected by the ext_authz service. If the + // ext_authz service tries injecting dynamic metadata, the filter will log, increment the + // ``ignored_dynamic_metadata`` stat, then continue handling the response. + // + // If true, the filter will ingest dynamic metadata entries as normal. + // + // If unset, defaults to true. + google.protobuf.BoolValue enable_dynamic_metadata_ingestion = 27; } // Configuration for buffering the request data. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 3b6321789f77..bbf3e9ebafb5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -398,6 +398,12 @@ new_features: change: | Added :ref:`bypass_overload_manager ` to bypass the overload manager for a listener. When set to true, the listener will not be subject to overload protection. +- area: ext_authz + change: | + Added + :ref:`enable_dynamic_metadata_ingestion + `, + which allows ext_authz to be configured to ignore dynamic metadata in ext_authz responses. - area: rbac change: | The RBAC filter will now log the enforced rule to the dynamic metadata field diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index 187721c466d6..470dcbd5e2fb 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -84,6 +84,8 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::ext_authz::v3 Filters::Common::MutationRules::Checker(config.decoder_header_mutation_rules(), factory_context.regexEngine())) : absl::nullopt), + enable_dynamic_metadata_ingestion_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enable_dynamic_metadata_ingestion, true)), runtime_(factory_context.runtime()), http_context_(factory_context.httpContext()), filter_enabled_(config.has_filter_enabled() ? absl::optional( @@ -404,18 +406,26 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { Stats::StatName empty_stat_name; if (!response->dynamic_metadata.fields().empty()) { - // Add duration of call to dynamic metadata if applicable - if (start_time_.has_value() && response->status == CheckStatus::OK) { - ProtobufWkt::Value ext_authz_duration_value; - auto duration = - decoder_callbacks_->dispatcher().timeSource().monotonicTime() - start_time_.value(); - ext_authz_duration_value.set_number_value( - std::chrono::duration_cast(duration).count()); - (*response->dynamic_metadata.mutable_fields())["ext_authz_duration"] = - ext_authz_duration_value; + if (!config_->enableDynamicMetadataIngestion()) { + ENVOY_STREAM_LOG(trace, + "Response is trying to inject dynamic metadata, but dynamic metadata " + "ingestion is disabled. Ignoring...", + *decoder_callbacks_); + stats_.ignored_dynamic_metadata_.inc(); + } else { + // Add duration of call to dynamic metadata if applicable + if (start_time_.has_value() && response->status == CheckStatus::OK) { + ProtobufWkt::Value ext_authz_duration_value; + auto duration = + decoder_callbacks_->dispatcher().timeSource().monotonicTime() - start_time_.value(); + ext_authz_duration_value.set_number_value( + std::chrono::duration_cast(duration).count()); + (*response->dynamic_metadata.mutable_fields())["ext_authz_duration"] = + ext_authz_duration_value; + } + decoder_callbacks_->streamInfo().setDynamicMetadata("envoy.filters.http.ext_authz", + response->dynamic_metadata); } - decoder_callbacks_->streamInfo().setDynamicMetadata("envoy.filters.http.ext_authz", - response->dynamic_metadata); } switch (response->status) { diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 96f1f02ccce6..fe1cb103789c 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -41,7 +41,8 @@ namespace ExtAuthz { COUNTER(error) \ COUNTER(disabled) \ COUNTER(failure_mode_allowed) \ - COUNTER(invalid) + COUNTER(invalid) \ + COUNTER(ignored_dynamic_metadata) /** * Wrapper struct for ext_authz filter stats. @see stats_macros.h @@ -91,6 +92,8 @@ class FilterConfig { return decoder_header_mutation_checker_.has_value(); } + bool enableDynamicMetadataIngestion() const { return enable_dynamic_metadata_ingestion_; } + Http::Code statusOnError() const { return status_on_error_; } bool validateMutations() const { return validate_mutations_; } @@ -182,6 +185,7 @@ class FilterConfig { const bool validate_mutations_; Stats::Scope& scope_; const absl::optional decoder_header_mutation_checker_; + const bool enable_dynamic_metadata_ingestion_; Runtime::Loader& runtime_; Http::Context& http_context_; LabelsMap destination_labels_; diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index 53f2fdf33583..c63186f01929 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -250,6 +250,44 @@ class InvalidMutationTest : public HttpFilterTestBase { const std::string invalid_value_; }; +TEST_F(HttpFilterTest, DisableDynamicMetadataIngestion) { + InSequence s; + + initialize(R"( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + enable_dynamic_metadata_ingestion: + value: false + )"); + + // Simulate a downstream request. + ON_CALL(decoder_filter_callbacks_, connection()) + .WillByDefault(Return(OptRef{connection_})); + connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr_); + connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr_); + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + + // Send response. Dynamic metadata should be ignored. + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, setDynamicMetadata(_, _)).Times(0); + + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + (*response.dynamic_metadata.mutable_fields())["key"] = ValueUtil::stringValue("value"); + request_callbacks_->onComplete(std::make_unique(response)); + + EXPECT_EQ(1U, config_->stats().ignored_dynamic_metadata_.value()); +} + // Tests that the filter rejects authz responses with mutations with an invalid key when // validate_authz_response is set to true in config. TEST_F(InvalidMutationTest, HeadersToSetKey) { From aef38c45a33e7c6a292f4e432a2b4940955ed96b Mon Sep 17 00:00:00 2001 From: Arul Thileeban Sagayam Date: Thu, 27 Jun 2024 19:30:47 -0400 Subject: [PATCH 13/31] tls: Add support for matching against OtherName SAN type (#34471) In the current SAN matcher, only DNS, URI, IP, EMAIL types are supported. This change adds support to match against OtherName. A new config field oid is added which helps define the type of OtherName SAN envoy needs to match against. Signed-off-by: Arul Thileeban Sagayam --- .../transport_sockets/tls/v3/common.proto | 16 + changelogs/current.yaml | 6 + ...tificate_validation_context_config_impl.cc | 1 + .../common/tls/cert_validator/san_matcher.cc | 16 +- .../common/tls/cert_validator/san_matcher.h | 7 +- source/common/tls/utility.cc | 130 ++++++ .../cert_validator/default_validator_test.cc | 369 ++++++++++++++++++ .../tls/cert_validator/san_matcher_test.cc | 15 + test/common/tls/test_data/README.md | 13 + test/common/tls/test_data/certs.sh | 16 + .../test_data/san_dns_and_othername_cert.cfg | 37 ++ .../test_data/san_dns_and_othername_cert.pem | 25 ++ .../san_dns_and_othername_cert_info.h | 13 + .../test_data/san_dns_and_othername_key.pem | 28 ++ .../test_data/san_multiple_othername_cert.cfg | 41 ++ .../test_data/san_multiple_othername_cert.pem | 26 ++ .../san_multiple_othername_cert_info.h | 13 + .../test_data/san_multiple_othername_key.pem | 28 ++ ...an_multiple_othername_string_type_cert.cfg | 59 +++ ...an_multiple_othername_string_type_cert.pem | 36 ++ ...multiple_othername_string_type_cert_info.h | 15 + ...san_multiple_othername_string_type_key.pem | 28 ++ .../tls/test_data/san_othername_cert.cfg | 36 ++ .../tls/test_data/san_othername_cert.pem | 25 ++ .../tls/test_data/san_othername_cert_info.h | 10 + .../tls/test_data/san_othername_key.pem | 28 ++ tools/spelling/spelling_dictionary.txt | 1 + 27 files changed, 1035 insertions(+), 3 deletions(-) create mode 100644 test/common/tls/test_data/san_dns_and_othername_cert.cfg create mode 100644 test/common/tls/test_data/san_dns_and_othername_cert.pem create mode 100644 test/common/tls/test_data/san_dns_and_othername_cert_info.h create mode 100644 test/common/tls/test_data/san_dns_and_othername_key.pem create mode 100644 test/common/tls/test_data/san_multiple_othername_cert.cfg create mode 100644 test/common/tls/test_data/san_multiple_othername_cert.pem create mode 100644 test/common/tls/test_data/san_multiple_othername_cert_info.h create mode 100644 test/common/tls/test_data/san_multiple_othername_key.pem create mode 100644 test/common/tls/test_data/san_multiple_othername_string_type_cert.cfg create mode 100644 test/common/tls/test_data/san_multiple_othername_string_type_cert.pem create mode 100644 test/common/tls/test_data/san_multiple_othername_string_type_cert_info.h create mode 100644 test/common/tls/test_data/san_multiple_othername_string_type_key.pem create mode 100644 test/common/tls/test_data/san_othername_cert.cfg create mode 100644 test/common/tls/test_data/san_othername_cert.pem create mode 100644 test/common/tls/test_data/san_othername_cert_info.h create mode 100644 test/common/tls/test_data/san_othername_key.pem diff --git a/api/envoy/extensions/transport_sockets/tls/v3/common.proto b/api/envoy/extensions/transport_sockets/tls/v3/common.proto index da68e8af5076..c1a3f5b33b34 100644 --- a/api/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -314,13 +314,29 @@ message SubjectAltNameMatcher { DNS = 2; URI = 3; IP_ADDRESS = 4; + OTHER_NAME = 5; } // Specification of type of SAN. Note that the default enum value is an invalid choice. SanType san_type = 1 [(validate.rules).enum = {defined_only: true not_in: 0}]; // Matcher for SAN value. + // + // The string matching for OTHER_NAME SAN values depends on their ASN.1 type: + // + // * OBJECT: Validated against its dotted numeric notation (e.g., "1.2.3.4") + // * BOOLEAN: Validated against strings "true" or "false" + // * INTEGER/ENUMERATED: Validated against a string containing the integer value + // * NULL: Validated against an empty string + // * Other types: Validated directly against the string value type.matcher.v3.StringMatcher matcher = 2 [(validate.rules).message = {required: true}]; + + // OID Value which is required if OTHER_NAME SAN type is used. + // For example, UPN OID is 1.3.6.1.4.1.311.20.2.3 + // (Reference: http://oid-info.com/get/1.3.6.1.4.1.311.20.2.3). + // + // If set for SAN types other than OTHER_NAME, it will be ignored. + string oid = 3; } // [#next-free-field: 18] diff --git a/changelogs/current.yaml b/changelogs/current.yaml index bbf3e9ebafb5..348a1eb46d7a 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -414,6 +414,12 @@ new_features: Added :ref:`strip_failure_response ` to allow stripping the failure response details from the JWT authentication filter. +- area: tls + change: | + added support to match against ``OtherName`` SAN Type under :ref:`match_typed_subject_alt_names + `. + An additional field ``oid`` is added to :ref:`SubjectAltNameMatcher + ` to support this change. deprecated: - area: tracing diff --git a/source/common/ssl/certificate_validation_context_config_impl.cc b/source/common/ssl/certificate_validation_context_config_impl.cc index 4f030296efcd..e6c9264024c8 100644 --- a/source/common/ssl/certificate_validation_context_config_impl.cc +++ b/source/common/ssl/certificate_validation_context_config_impl.cc @@ -100,6 +100,7 @@ CertificateValidationContextConfigImpl::getSubjectAltNameMatchers( } // Handle deprecated string type san matchers without san type specified, by // creating a matcher for each supported type. + // Note: This does not handle otherName type for (const envoy::type::matcher::v3::StringMatcher& matcher : config.match_subject_alt_names()) { static constexpr std::array< envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType, 4> diff --git a/source/common/tls/cert_validator/san_matcher.cc b/source/common/tls/cert_validator/san_matcher.cc index 0229ca1c1273..99dc011c6b6c 100644 --- a/source/common/tls/cert_validator/san_matcher.cc +++ b/source/common/tls/cert_validator/san_matcher.cc @@ -18,6 +18,11 @@ bool StringSanMatcher::match(const GENERAL_NAME* general_name) const { if (general_name->type != general_name_type_) { return false; } + if (general_name->type == GEN_OTHERNAME) { + if (OBJ_cmp(general_name->d.otherName->type_id, general_name_oid_.get())) { + return false; + } + } // For DNS SAN, if the StringMatcher type is exact, we have to follow DNS matching semantics. const std::string san = Utility::generalNameAsString(general_name); return general_name->type == GEN_DNS && @@ -32,7 +37,7 @@ SanMatcherPtr createStringSanMatcher( Server::Configuration::CommonFactoryContext& context) { // Verify that a new san type has not been added. static_assert(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType_MAX == - 4); + 5); switch (matcher.san_type()) { PANIC_ON_PROTO_ENUM_SENTINEL_VALUES; @@ -44,6 +49,15 @@ SanMatcherPtr createStringSanMatcher( return SanMatcherPtr{std::make_unique(GEN_URI, matcher.matcher(), context)}; case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS: return SanMatcherPtr{std::make_unique(GEN_IPADD, matcher.matcher(), context)}; + case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::OTHER_NAME: { + // Invalid/Empty OID returns a nullptr from OBJ_txt2obj + bssl::UniquePtr oid(OBJ_txt2obj(matcher.oid().c_str(), 0)); + if (oid == nullptr) { + return nullptr; + } + return SanMatcherPtr{std::make_unique(GEN_OTHERNAME, matcher.matcher(), + context, std::move(oid))}; + } case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SAN_TYPE_UNSPECIFIED: PANIC("unhandled value"); } diff --git a/source/common/tls/cert_validator/san_matcher.h b/source/common/tls/cert_validator/san_matcher.h index 2a8af500a10b..135cf17f701c 100644 --- a/source/common/tls/cert_validator/san_matcher.h +++ b/source/common/tls/cert_validator/san_matcher.h @@ -35,12 +35,15 @@ class StringSanMatcher : public SanMatcher { bool match(const GENERAL_NAME* general_name) const override; ~StringSanMatcher() override = default; StringSanMatcher(int general_name_type, envoy::type::matcher::v3::StringMatcher matcher, - Server::Configuration::CommonFactoryContext& context) - : general_name_type_(general_name_type), matcher_(matcher, context) {} + Server::Configuration::CommonFactoryContext& context, + bssl::UniquePtr&& general_name_oid = nullptr) + : general_name_type_(general_name_type), matcher_(matcher, context), + general_name_oid_(std::move(general_name_oid)) {} private: const int general_name_type_; const Matchers::StringMatcherImpl matcher_; + bssl::UniquePtr general_name_oid_; }; SanMatcherPtr createStringSanMatcher( diff --git a/source/common/tls/utility.cc b/source/common/tls/utility.cc index 4ca9f26ed614..6df4276a4e99 100644 --- a/source/common/tls/utility.cc +++ b/source/common/tls/utility.cc @@ -226,6 +226,136 @@ std::string Utility::generalNameAsString(const GENERAL_NAME* general_name) { } break; } + case GEN_OTHERNAME: { + ASN1_TYPE* value = general_name->d.otherName->value; + if (value == nullptr) { + break; + } + switch (value->type) { + case V_ASN1_NULL: + break; + case V_ASN1_BOOLEAN: + san = value->value.boolean ? "true" : "false"; + break; + case V_ASN1_ENUMERATED: + case V_ASN1_INTEGER: { + BIGNUM san_bn; + BN_init(&san_bn); + value->type == V_ASN1_ENUMERATED ? ASN1_ENUMERATED_to_BN(value->value.enumerated, &san_bn) + : ASN1_INTEGER_to_BN(value->value.integer, &san_bn); + char* san_char = BN_bn2dec(&san_bn); + BN_free(&san_bn); + if (san_char != nullptr) { + san.assign(san_char); + OPENSSL_free(san_char); + } + break; + } + case V_ASN1_OBJECT: { + char tmp_obj[256]; // OID Max length + int obj_len = OBJ_obj2txt(tmp_obj, 256, value->value.object, 1); + if (obj_len > 256 || obj_len < 0) { + break; + } + san.assign(tmp_obj); + break; + } + case V_ASN1_BIT_STRING: { + ASN1_BIT_STRING* tmp_str = value->value.bit_string; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_OCTET_STRING: { + ASN1_OCTET_STRING* tmp_str = value->value.octet_string; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_PRINTABLESTRING: { + ASN1_PRINTABLESTRING* tmp_str = value->value.printablestring; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_T61STRING: { + ASN1_T61STRING* tmp_str = value->value.t61string; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_IA5STRING: { + ASN1_IA5STRING* tmp_str = value->value.ia5string; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_GENERALSTRING: { + ASN1_GENERALSTRING* tmp_str = value->value.generalstring; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_BMPSTRING: { + // `ASN1_BMPSTRING` is encoded using `UCS-4`, which needs conversion to UTF-8. + unsigned char* tmp = nullptr; + if (ASN1_STRING_to_UTF8(&tmp, value->value.bmpstring) < 0) { + break; + } + san.assign(reinterpret_cast(tmp)); + OPENSSL_free(tmp); + break; + } + case V_ASN1_UNIVERSALSTRING: { + // `ASN1_UNIVERSALSTRING` is encoded using `UCS-4`, which needs conversion to UTF-8. + unsigned char* tmp = nullptr; + if (ASN1_STRING_to_UTF8(&tmp, value->value.universalstring) < 0) { + break; + } + san.assign(reinterpret_cast(tmp)); + OPENSSL_free(tmp); + break; + } + case V_ASN1_UTCTIME: { + ASN1_UTCTIME* tmp_str = value->value.utctime; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_GENERALIZEDTIME: { + ASN1_GENERALIZEDTIME* tmp_str = value->value.generalizedtime; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_VISIBLESTRING: { + ASN1_VISIBLESTRING* tmp_str = value->value.visiblestring; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_UTF8STRING: { + ASN1_UTF8STRING* tmp_str = value->value.utf8string; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_SET: { + ASN1_STRING* tmp_str = value->value.set; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + case V_ASN1_SEQUENCE: { + ASN1_STRING* tmp_str = value->value.sequence; + san.assign(reinterpret_cast(ASN1_STRING_data(tmp_str)), + ASN1_STRING_length(tmp_str)); + break; + } + default: + break; + } + } } return san; } diff --git a/test/common/tls/cert_validator/default_validator_test.cc b/test/common/tls/cert_validator/default_validator_test.cc index 7d1b8cd5e3bc..b876ca65df6a 100644 --- a/test/common/tls/cert_validator/default_validator_test.cc +++ b/test/common/tls/cert_validator/default_validator_test.cc @@ -47,6 +47,375 @@ TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameDNSMatched) { EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); } +// All OtherName SAN tests below are matched against an expected OID +// and an expected SAN value + +// Test to check if the cert has an OtherName SAN +// of UTF8String type with value containing "example.com" +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_othername_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.com)raw")); + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.3", 0)); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with a Boolean type value "true" +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameBooleanTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.5.5.7.8.7", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("true")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with an Enumerated type value "5" +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameEnumeratedTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.1", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("5")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with an Integer type value "5464". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameIntegerTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.5.5.7.8.3", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("5464")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with an Object type value "1.3.6.1.4.1.311.20.2.3". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameObjectTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.5.2.2", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("1.3.6.1.4.1.311.20.2.3")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN with a NULL type. +// NULL value is matched against an empty string since matcher is required. +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameNullTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.3", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with a BitString type value "01010101". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameBitStringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.3", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("01010101")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with an OctetString type value "48656C6C6F20576F726C64". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameOctetStringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.4", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("48656C6C6F20576F726C64")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with a PrintableString type value "PrintableStringExample". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNamePrintableStringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.5", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("PrintableStringExample")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with a T61String type value "T61StringExample". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameT61StringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.6", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("T61StringExample")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with a IA5String type value "IA5StringExample". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameIA5StringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.7", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("IA5StringExample")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with a GeneralStringExample type value "GeneralStringExample". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameGeneralStringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.8", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("GeneralStringExample")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with an UniversalStringExample type value "UniversalStringExample". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameUniversalStringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.9", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("UniversalStringExample")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with an UTCTime type value "230616120000Z". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameUtcTimeTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.10", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("230616120000Z")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with a GeneralizedTime type value "20230616120000Z". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameGeneralizedTimeTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.11", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("20230616120000Z")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with a VisibleString type value "VisibleStringExample". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameVisibleStringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.12", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("VisibleStringExample")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with an UTF8 String type value "UTF8StringExample". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameUTF8StringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.13", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("UTF8StringExample")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN +// with a `BMPString` type value "BMPStringExample". +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameBmpStringTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.14", 0)); + matcher.MergeFrom(TestUtility::createExactMatcher("BMPStringExample")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN with a SET type +// containing select values "test1" and "test2". +// SET is a non-primitive type containing multiple values and is DER-encoded. +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameSetTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.3", 0)); + matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw(.*test1.*test2.*)raw")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +// Test to check if the cert has an OtherName SAN with a SEQUENCE type +// containing select values "test3" and "test4". +// SEQUENCE is a non-primitive type containing multiple values and is DER-encoded. +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameSequenceTypeMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.3", 0)); + matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw(.*test3.*test4.*)raw")); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameDnsAndOtherNameMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_dns_and_othername_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.com)raw")); + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.3", 0)); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back( + SanMatcherPtr{std::make_unique(GEN_DNS, matcher, context)}); + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameIncorrectOidMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_othername_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.com)raw")); + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.2", 0)); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameOtherNameIncorrectValueMatched) { + NiceMock context; + + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/tls/test_data/san_othername_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.net)raw")); + bssl::UniquePtr oid(OBJ_txt2obj("1.3.6.1.4.1.311.20.2.3", 0)); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(SanMatcherPtr{ + std::make_unique(GEN_OTHERNAME, matcher, context, std::move(oid))}); + EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameIncorrectTypeMatched) { NiceMock context; diff --git a/test/common/tls/cert_validator/san_matcher_test.cc b/test/common/tls/cert_validator/san_matcher_test.cc index 3b330866a379..42fe9cd41673 100644 --- a/test/common/tls/cert_validator/san_matcher_test.cc +++ b/test/common/tls/cert_validator/san_matcher_test.cc @@ -30,6 +30,10 @@ TEST(SanMatcherConfigTest, TestValidSanType) { envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher san_matcher; san_matcher.mutable_matcher()->set_exact("foo.example"); san_matcher.set_san_type(san_type); + if (san_type == + envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::OTHER_NAME) { + san_matcher.set_oid("1.3.6.1.4.1.311.20.2.3"); // Set dummy OID + } if (san_type == envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher:: SAN_TYPE_UNSPECIFIED) { EXPECT_DEATH(createStringSanMatcher(san_matcher, context), "unhandled value"); @@ -42,6 +46,17 @@ TEST(SanMatcherConfigTest, TestValidSanType) { } } +// Verify that setting Invalid OID for OtherName SAN results in a panic. +TEST(SanMatcherConfigTest, TestInvalidOidOtherNameSanType) { + NiceMock context; + envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher san_matcher; + san_matcher.mutable_matcher()->set_exact("foo.example"); + san_matcher.set_oid("1.3.6.1.4.1.311.20.2.ffff"); + san_matcher.set_san_type( + envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::OTHER_NAME); + EXPECT_EQ(createStringSanMatcher(san_matcher, context), nullptr); +} + TEST(SanMatcherConfigTest, UnspecifiedSanType) { NiceMock context; envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher san_matcher; diff --git a/test/common/tls/test_data/README.md b/test/common/tls/test_data/README.md index 6b16516074cf..57c7b5a114d0 100644 --- a/test/common/tls/test_data/README.md +++ b/test/common/tls/test_data/README.md @@ -29,6 +29,19 @@ There are 15 identities: - **SAN With URI**: It has the certificate *san_uri_cert.pem*, which is signed by the **CA** using the config *san_uri_cert.cfg*. The certificate has SAN field of URI type. *san_uri_key.pem* is its private key. +- **SAN With OtherName**: It has the certificate *san_othername_cert.pem*, which is signed + by the **CA** using the config *san_othername_cert.cfg*. The certificate has SAN + field of OtherName(UPN) type. *san_othername_key.pem* is its private key. +- **SAN With OtherName and DNS**: It has the certificate *san_dns_and_othername_cert.pem*, which is signed + by the **CA** using the config *san_dns_and_othername_cert.cfg*. The certificate has two SAN + fields, one DNS and one OtherName(UPN) type. *san_dns_and_othername_key.key* is its private key. +- **SAN With Multiple Othername**: It has the certificate *san_multiple_othername_cert.pem*, which is signed + by the **CA** using the config *san_dns_and_othername_cert.cfg*. The certificate has 5 SAN fields + of NULL, ENUMERATED, INTEGER, BOOLEAN and OBJECT types. *san_multiple_othername_key.pem* is its private key. +- **SAN With Multiple Othername (String Type)**: It has the certificate + *san_multiple_othername_string_type_cert.pem*, which is signed by the **CA** using the config + *san_multiple_othername_string_type_key.cfg*. The certificate has two SANfields, one DNS and one + OtherName(UPN) type. *san_multiple_othername_string_type_key.pem* is its private key. - **Password-protected**: The password-protected certificate *password_protected_cert.pem*, using the config *san_uri_cert.cfg*. *password_protected_key.pem* is its private key encrypted using the password supplied in *password_protected_password.txt*. diff --git a/test/common/tls/test_data/certs.sh b/test/common/tls/test_data/certs.sh index 3c78220a084f..cfc93a6abe45 100755 --- a/test/common/tls/test_data/certs.sh +++ b/test/common/tls/test_data/certs.sh @@ -234,6 +234,22 @@ generate_x509_cert san_uri ca generate_rsa_key san_ip generate_x509_cert san_ip ca +# Generate san_othername_cert.pem. +generate_rsa_key san_othername +generate_x509_cert san_othername ca + +# Generate san_dns_and_othername_cert.pem. +generate_rsa_key san_multiple_othername +generate_x509_cert san_multiple_othername ca + +# Generate san_multiple_othername_string_type.pem. +generate_rsa_key san_multiple_othername_string_type +generate_x509_cert san_multiple_othername_string_type ca + +# Generate san_dns_and_othername_cert.pem. +generate_rsa_key san_dns_and_othername +generate_x509_cert san_dns_and_othername ca + # Concatenate san_ip_cert.pem and Test Intermediate CA (intermediate_ca_cert.pem) to create valid certificate chain. cat san_ip_cert.pem intermediate_ca_cert.pem > san_ip_chain.pem diff --git a/test/common/tls/test_data/san_dns_and_othername_cert.cfg b/test/common/tls/test_data/san_dns_and_othername_cert.cfg new file mode 100644 index 000000000000..6fb9d225dbe4 --- /dev/null +++ b/test/common/tls/test_data/san_dns_and_othername_cert.cfg @@ -0,0 +1,37 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = US +countryName_default = US +stateOrProvinceName = California +stateOrProvinceName_default = California +localityName = San Francisco +localityName_default = San Francisco +organizationName = Lyft +organizationName_default = Lyft +organizationalUnitName = Lyft Engineering +organizationalUnitName_default = Lyft Engineering +commonName = Test Server +commonName_default = Test Server +commonName_max = 64 + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash + +[v3_ca] +basicConstraints = critical, CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +[alt_names] +DNS.1 = server1.example.com +otherName.1 = 1.3.6.1.4.1.311.20.2.3;UTF8:server1.example.com diff --git a/test/common/tls/test_data/san_dns_and_othername_cert.pem b/test/common/tls/test_data/san_dns_and_othername_cert.pem new file mode 100644 index 000000000000..6b8ccd2eea06 --- /dev/null +++ b/test/common/tls/test_data/san_dns_and_othername_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEQTCCAymgAwIBAgIUOdN440ZeucQGIFJ7uRJczkvWIIowDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNjAxMjIyMTMxWhcNMjYw +NjAxMjIyMTMxWjB6MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzEUMBIGA1UEAwwLVGVzdCBTZXJ2ZXIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4ciZSrbe8JTZivyiEUdTjPHpWZp3dNuqf +1dZduzAl9aQG8CMQhidwgI721TVRLYwDum1Eusky+nq3+X0JOBBsrs6A+mGZu/Em +W2PLmpJhAmsQG9iqOhgJuoAUmexCD/Zrv1HQJYsCEKZCV7qjbScj3qvwFDW+Zs4R +/Q8/pzMpQV6ktC+n4cc81DAK6AUZVeMmRSFefNudloZNNYyKI0XOuURMBi/+ZN+G +L/xzzzzFzH1Q9QLVAL+ySxquhuwqLp2md7Ed/821+DZvLJfKrW2nDDcQIC8FEIfy +w92WHv4Qth12f0jx/GR1AFTUiGgeNyQbjJU+bkfBmyRXDPe5EQ8RAgMBAAGjgcIw +gb8wDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH +AwIGCCsGAQUFBwMBMEMGA1UdEQQ8MDqCE3NlcnZlcjEuZXhhbXBsZS5jb22gIwYK +KwYBBAGCNxQCA6AVDBNzZXJ2ZXIxLmV4YW1wbGUuY29tMB0GA1UdDgQWBBQwzMfn +25vM1z02zWxMuZChTnm8wjAfBgNVHSMEGDAWgBTQumAzod6zDNdAA8q1kU0sEGXw +mzANBgkqhkiG9w0BAQsFAAOCAQEAaSGH+VONB4BbU12K3xuCsdWYixfJFmNdhEvw +SLGqAY7z2PnybNzgRaGMBPrcgL8Uw88faqbGCVd6ewsnesvUz9VACTksT9XjaCkW +h3xF7W4GbrJQWU02vHOLUda0/89VLdpQcM76z4ZRCKDHrCNO/4ncOXl87haKAELz +8QwhDIk09nEDbDdOYCQAG51iRTVBRmBnT2hShof89z1c0rRgEMLQ5u8EXk7rtAXu +Mldg1N5ll93l7aC87/8iqNWX+3j8P1ynmjbjclpTrMbHk/q4lybDcsjTpjOJgSSZ +dP2MpZEfAXL/taDEf2siOUxmjFO9gIoGTjlM4SoNvA5IVyHefw== +-----END CERTIFICATE----- diff --git a/test/common/tls/test_data/san_dns_and_othername_cert_info.h b/test/common/tls/test_data/san_dns_and_othername_cert_info.h new file mode 100644 index 000000000000..143543312963 --- /dev/null +++ b/test/common/tls/test_data/san_dns_and_othername_cert_info.h @@ -0,0 +1,13 @@ +#pragma once + +// NOLINT(namespace-envoy) +constexpr char TEST_SAN_DNS_AND_OTHERNAME_CERT_256_HASH[] = + "10b81b089fa82a542ef4637cd058e8a8ce9aac13703bc7c0f3b18eecd0bafa01"; +constexpr char TEST_SAN_DNS_AND_OTHERNAME_CERT_1_HASH[] = + "b2cdaab75e39c57abb9134271432a15a7078a83c"; +constexpr char TEST_SAN_DNS_AND_OTHERNAME_CERT_SPKI[] = + "orMj1YQ0Kw6uh/WkfJ/IITdlgbQTF+Lb1Jj+b6R+J+w="; +constexpr char TEST_SAN_DNS_AND_OTHERNAME_CERT_SERIAL[] = + "39d378e3465eb9c40620527bb9125cce4bd6208a"; +constexpr char TEST_SAN_DNS_AND_OTHERNAME_CERT_NOT_BEFORE[] = "Jun 1 22:21:31 2024 GMT"; +constexpr char TEST_SAN_DNS_AND_OTHERNAME_CERT_NOT_AFTER[] = "Jun 1 22:21:31 2026 GMT"; diff --git a/test/common/tls/test_data/san_dns_and_othername_key.pem b/test/common/tls/test_data/san_dns_and_othername_key.pem new file mode 100644 index 000000000000..38ce99ea8d21 --- /dev/null +++ b/test/common/tls/test_data/san_dns_and_othername_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4ciZSrbe8JTZi +vyiEUdTjPHpWZp3dNuqf1dZduzAl9aQG8CMQhidwgI721TVRLYwDum1Eusky+nq3 ++X0JOBBsrs6A+mGZu/EmW2PLmpJhAmsQG9iqOhgJuoAUmexCD/Zrv1HQJYsCEKZC +V7qjbScj3qvwFDW+Zs4R/Q8/pzMpQV6ktC+n4cc81DAK6AUZVeMmRSFefNudloZN +NYyKI0XOuURMBi/+ZN+GL/xzzzzFzH1Q9QLVAL+ySxquhuwqLp2md7Ed/821+DZv +LJfKrW2nDDcQIC8FEIfyw92WHv4Qth12f0jx/GR1AFTUiGgeNyQbjJU+bkfBmyRX +DPe5EQ8RAgMBAAECggEAHxShL5J1YR92LCqhJbbyZD5HMTMGjAXagIeUoWPPJ75e +XwrtJbYthDAtpxtjaiP+MYyjKA8/ozcBIepJLxoC7oWAZ8yJUNISP0/sH52S6ATz +zJmcp4a1kUIbnh0X6kPtVte87hG9fGIY2hoVab+Vdl5p48FMEyMYu4BEpwnCPcOs +RpZokVAyuJnqlPhcxU0BctmjmGQIFHqQ7G0stbukfispbR6+iZoo8ltVh0lemBS9 +hJ1E2SCXQSiC6m/0+I1jRA3kgmyB/0XArpXP+TmTcNiiNyDtSbMQmtw1v1Z8CPfB +2LJGIMhmICQ9BwUvNHAPbG7hHyQU2bM4GIERL8lmHwKBgQDpciPHbLxOtmmAyY5w +gmONYiou/EjizmSWdxTHPcsnzlqUUw7ds+1ieNLUn3icaWhwzY4zJ6lHT0ZqmzHB +w7TuIBK5BgBmqUQIYMv45Rn+r3na5nP080QqBrkr3ZXOhfg5uPAwiFbKEsRMJZ/2 +CP6PiaRTIUtjKvb3buHmF06x1wKBgQDKRBWu28vo9TUY6ssLzlfK8CK5rprWqdXd +LQUZ13f/XEd4KFgfNBG77jDBQxaXbP4G2VDHTLCOzA6egWY3qN6BGDv+wD7Q7yde +HT+KOuGMvrT00MgdlrLfTBjGM0BC1GeFc90UnBOfWumWKa5551btdTazoYe7EIyD +JZghnlx5VwKBgDrmAF71cUFOxqmmsNh0HVfzl38JSf5nYnuQCd8HGTWu262mkw6e +sdrxbwgUQCL+eUpUoncHn68NMk/9Xf1sOj8GOpMSD5HXTQHsIipm6zsV3OG82S7J +Hb6Yual2m7BinrE5lug3zeXn/DzWFVjHBisC6EHNGa8ojOz6veYGpWU7AoGAL2fF +rTXWlMLjrvNYo2u5J9cgTGSf5a/ob+4dQ/E8Lp1yIrdR7/5EKceppaITqWniH7jP +NebDerRYuM2bJ3BstdT4OrzT/CQRFf3E5qDmPBZ2Uuqb/FNVmQA8zjc02HTvzldZ +eXsbHj4wgQFD405VEVJnf7JcHXvDcvlcroRvKAECgYEA3aPHpiHEoXAPyUSAvZuh +OwM6+GIxWJpqZ+EFqIfTO7BPWnZT0skjxJlMAgTO+LJaczcU49ooXWeFcNFBr9sJ +iyd70CBXrmQQjMzRwafbAHach/o4LREphtr73Q6xvE2OCvFqCHQkB1mRRl9Ikoe5 +lO/82VvYXXW/iiNsFxvOQ+w= +-----END PRIVATE KEY----- diff --git a/test/common/tls/test_data/san_multiple_othername_cert.cfg b/test/common/tls/test_data/san_multiple_othername_cert.cfg new file mode 100644 index 000000000000..ac7556cd1be8 --- /dev/null +++ b/test/common/tls/test_data/san_multiple_othername_cert.cfg @@ -0,0 +1,41 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = US +countryName_default = US +stateOrProvinceName = California +stateOrProvinceName_default = California +localityName = San Francisco +localityName_default = San Francisco +organizationName = Lyft +organizationName_default = Lyft +organizationalUnitName = Lyft Engineering +organizationalUnitName_default = Lyft Engineering +commonName = Test Server +commonName_default = Test Server +commonName_max = 64 + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash + +[v3_ca] +basicConstraints = critical, CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +[alt_names] +otherName.1 = 1.3.6.1.5.5.7.8.7;BOOLEAN:true +otherName.2 = 1.3.6.1.4.1.311.20.2.1;ENUMERATED:5 +otherName.3 = 1.3.6.1.5.5.7.8.3;INTEGER:5464 +otherName.4 = 1.3.6.1.5.2.2;OBJECT:1.3.6.1.4.1.311.20.2.3 +otherName.5 = 1.3.6.1.4.1.311.20.2.3;NULL: + diff --git a/test/common/tls/test_data/san_multiple_othername_cert.pem b/test/common/tls/test_data/san_multiple_othername_cert.pem new file mode 100644 index 000000000000..8502ccea1c13 --- /dev/null +++ b/test/common/tls/test_data/san_multiple_othername_cert.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEZzCCA0+gAwIBAgIUYMY2k+ExGUq1HxP0rNwxBdokgFcwDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNjA4MDUzNDU0WhcNMjYw +NjA4MDUzNDU0WjB6MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzEUMBIGA1UEAwwLVGVzdCBTZXJ2ZXIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOfxPpXuRJBWsAA0lsPhPYY0dbRNUHF2gN +SLLnPtNiNrQ3FW30U5rCG7tL8EoWK9OI48li8EKvU0LzuxJrZsu7JSqkuOLJntpX +N1qcp8AsawyvAKWbY9MC8h9d/QpNWEUNFivbcg2XMKHCIdjm37Wu75/B8kDP9uC4 +QpHcs90VrMe3cm8G9GCgP+kGr3YZVxeNBTT/t7nKFir9K+t+1T/BpP3fd6li9a61 +Qjyece2aEi4p+GMjFJICRLa/eHmA/GlIbn99d5H51clH1oRQ5BYNgBJJ9z85NgJZ +Nhy4Yk4eNeE/o1n2s4aZQrcr8C/32QUKcApjzFaqmbPBq4Yr7ZsrAgMBAAGjgegw +geUwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH +AwIGCCsGAQUFBwMBMGkGA1UdEQRiMGCgDwYIKwYBBQUHCAegAwEB/6ARBgorBgEE +AYI3FAIBoAMKAQWgEAYIKwYBBQUHCAOgBAICFVigFgYGKwYBBQICoAwGCisGAQQB +gjcUAgOgEAYKKwYBBAGCNxQCA6ACBQAwHQYDVR0OBBYEFAihz/5otdvcS7Yx9g9i +j00nO48/MB8GA1UdIwQYMBaAFEmZclc8R2TQpc5VDVyAgf/Gg1J5MA0GCSqGSIb3 +DQEBCwUAA4IBAQCP4B5R8BDLalXpmiKAQuGAylJLu3hQ/L/4/9hz9Lzuqkjkf5PA +J49EYfCwl1dHKNXnD47m9v/t0Bf1V/fk7DdWublHNGLnnSSTXbCTOcRqswqfbWe7 +m/eRoZdTM42oON1MNF9PGoGstZrZkH3wzPPdsJwEP2ahWEbvMxmO8kgrslyw1cpE +Rm2bvsiqCZBkieHoYD5fE5tADBHi1+byUUTBZWPgoNOvJLoPcaFjU2Pfspuv9bhV +9J46OTI3BkYFebLXCeP8D11EGXa/OgHyS21hIHcIKrS13STdeuOBftX9lYoc7Wea +atmMcw8vRehWJU/IuXWiHY8CDi1Xyl8tcCVY +-----END CERTIFICATE----- diff --git a/test/common/tls/test_data/san_multiple_othername_cert_info.h b/test/common/tls/test_data/san_multiple_othername_cert_info.h new file mode 100644 index 000000000000..e5fb16fc4a7f --- /dev/null +++ b/test/common/tls/test_data/san_multiple_othername_cert_info.h @@ -0,0 +1,13 @@ +#pragma once + +// NOLINT(namespace-envoy) +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_CERT_256_HASH[] = + "86f54db08902ae7434103b175c54b0941b769ba2603d94178cf1ab077496663d"; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_CERT_1_HASH[] = + "8e55e60f04e0eba9bb542c549348c3458e3af11a"; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_CERT_SPKI[] = + "A9W0Y6EHfbfUBN/3zxyBWWKkOajQdPcxYy2hjNIeWSQ="; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_CERT_SERIAL[] = + "60c63693e131194ab51f13f4acdc3105da248057"; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_CERT_NOT_BEFORE[] = "Jun 8 05:34:54 2024 GMT"; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_CERT_NOT_AFTER[] = "Jun 8 05:34:54 2026 GMT"; diff --git a/test/common/tls/test_data/san_multiple_othername_key.pem b/test/common/tls/test_data/san_multiple_othername_key.pem new file mode 100644 index 000000000000..9b5b39fc75f2 --- /dev/null +++ b/test/common/tls/test_data/san_multiple_othername_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDOfxPpXuRJBWsA +A0lsPhPYY0dbRNUHF2gNSLLnPtNiNrQ3FW30U5rCG7tL8EoWK9OI48li8EKvU0Lz +uxJrZsu7JSqkuOLJntpXN1qcp8AsawyvAKWbY9MC8h9d/QpNWEUNFivbcg2XMKHC +Idjm37Wu75/B8kDP9uC4QpHcs90VrMe3cm8G9GCgP+kGr3YZVxeNBTT/t7nKFir9 +K+t+1T/BpP3fd6li9a61Qjyece2aEi4p+GMjFJICRLa/eHmA/GlIbn99d5H51clH +1oRQ5BYNgBJJ9z85NgJZNhy4Yk4eNeE/o1n2s4aZQrcr8C/32QUKcApjzFaqmbPB +q4Yr7ZsrAgMBAAECggEAY+ITKxSj3vBYd/qfBtUmO0qWZv7t/k6jnZ1XMETy8bRd +SrG6bG8OUisg11QlOgE8AqCQNqPZ1b20CnooKDxieqU5MdFencERgxN66IC6E6I6 +UeJBuN654FhmtMtjstLqH7DkSPMrwMCc1e8SYGbAlpxBDgrUr+OOgoJs1LFJ1SPN +2M3/6o1hWEfK/OlxrD3sd05L2vogQgyBQ4aJUf3HLMZXJx5iJMbzap/EeVXk/b6K +Odm6vtumaAL0q2CI5NsIbPCC2KK/0xw7jvmRSf9mb3PzEjuiDTn7mUuykTfDChEU +Fxtfq5ECXQlfGqKrGoFu48yV3mttBGv3yk7SraYdvQKBgQD1OslT/PEZR6ll28WI +xIAlh/QCHLmpIBWSp16KfQB+K+KZTOvRDkbUkX5O+rAxlJIH6U+jqwzeA5CzCPI8 +8jdFRd1f/APdqINpkbfzQo6lAUUhCc6dlN45wfbV6nptGoIFN0cUgOjZLtTTVI2/ +x9IpiNmH9CvcRAEzCEOAZVnShQKBgQDXkMxbmEtKHFKiiyzeclS0atqO3pEyPrZI ++eZTXgVpBQhdRLao35L+ay0dsU38CA5mqxiAR+BQ/KtT56V/vUYfIOlRd9ZT5n9c +kHU5O8cOuH3N0UmSzeEfUwjMwCqjQgE22Fn0l3tJc6dZxecZEiOFxynOiUfWRhuW +h7ZbFyUd7wKBgQCnWbrS6ibOjaz4qgYf05lwA1ttpZS96ftO3ZETCUMw11oILAox +3IBRyAhedY2QCEevxnRmyPA7AkvZIh3Noa4+Q/NS8XTh64HipWLXS3B1Amzeowax +W0pcOBXu3dk2Y4Sfcp27TE2bCO6MWYNygTbWyWFJ+kOESZRX8ye9k2y80QKBgQCj +zMRsaUn0k5b7KjQ7B7dzrKpM27SK8HpE85dgC9aimY9kh90gb1rb9oa+xEbU1y5S +N3qTp4o9H/Hz/NaWPTW2W6TPIfd7o29t39sjVVgJyIjXx0tXwRdqXQcXxoHfsj0H +9thL8ntdMgOdRLM5Kr5RXihXZ5ttp7I47QDVML4kgwKBgF+V4dpMIXBLkO6aVta1 +CBoQgds6dxEKb3LFIf/I0bfBhPgbmpqMLEwwJOtpkQLVUayoJ16CYhw2N02PR6Xh +E9zeuhpzn7MZira2OAsAqzMbfYkiZvvG4ocYsAbZB52pl1x/EtnG00B3IZplP5Vl +k8CjywzwgcTeJQc3J2aZpg+6 +-----END PRIVATE KEY----- diff --git a/test/common/tls/test_data/san_multiple_othername_string_type_cert.cfg b/test/common/tls/test_data/san_multiple_othername_string_type_cert.cfg new file mode 100644 index 000000000000..3907a6046c46 --- /dev/null +++ b/test/common/tls/test_data/san_multiple_othername_string_type_cert.cfg @@ -0,0 +1,59 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = US +countryName_default = US +stateOrProvinceName = California +stateOrProvinceName_default = California +localityName = San Francisco +localityName_default = San Francisco +organizationName = Lyft +organizationName_default = Lyft +organizationalUnitName = Lyft Engineering +organizationalUnitName_default = Lyft Engineering +commonName = Test Server +commonName_default = Test Server +commonName_max = 64 + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash + +[v3_ca] +basicConstraints = critical, CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +# OtherNames 1, 13 and 14 contain the same OID to help tests simulate +# certs with OtherName SAN having same OID, but multiple & different SAN values +[alt_names] +otherName.1 = 1.3.6.1.4.1.311.20.2.3;BITSTRING:01010101 +otherName.2 = 1.3.6.1.4.1.311.20.2.4;OCTETSTRING:48656C6C6F20576F726C64 +otherName.3 = 1.3.6.1.4.1.311.20.2.5;PRINTABLESTRING:PrintableStringExample +otherName.4 = 1.3.6.1.4.1.311.20.2.6;T61STRING:T61StringExample +otherName.5 = 1.3.6.1.4.1.311.20.2.7;IA5STRING:IA5StringExample +otherName.6 = 1.3.6.1.4.1.311.20.2.8;GENERALSTRING:GeneralStringExample +otherName.7 = 1.3.6.1.4.1.311.20.2.9;UNIVERSALSTRING:UniversalStringExample +otherName.8 = 1.3.6.1.4.1.311.20.2.10;UTCTIME:230616120000Z +otherName.9 = 1.3.6.1.4.1.311.20.2.11;GENERALIZEDTIME:20230616120000Z +otherName.10 = 1.3.6.1.4.1.311.20.2.12;VISIBLESTRING:VisibleStringExample +otherName.11 = 1.3.6.1.4.1.311.20.2.13;UTF8STRING:UTF8StringExample +otherName.12 = 1.3.6.1.4.1.311.20.2.14;BMPSTRING:BMPStringExample +otherName.13 = 1.3.6.1.4.1.311.20.2.3;SET:test_set +otherName.14 = 1.3.6.1.4.1.311.20.2.3;SEQUENCE:test_sequence + +[test_set] +field1 = UTF8:test1 +field2 = UTF8:test2 + +[test_sequence] +field1 = UTF8:test3 +field2 = UTF8:test4 diff --git a/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem b/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem new file mode 100644 index 000000000000..e871488d63b0 --- /dev/null +++ b/test/common/tls/test_data/san_multiple_othername_string_type_cert.pem @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGSTCCBTGgAwIBAgIUGQ0pXoKJwThBxYJ94e/AeW5ELnUwDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNjI3MjEyNTUxWhcNMjYw +NjI3MjEyNTUxWjB6MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzEUMBIGA1UEAwwLVGVzdCBTZXJ2ZXIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkIyDAeOKpM8eJYhQ8oTvDt2c2NXVX3Rwv +5+DxpdzJLNuYld9ox87zOgRXHLYRz/G+3exRXqIVRAy1BYQfHMcLAvTYvCiRmG5C +zH24ulfwCpA0xvAGcuCMVPK4PgXLdWNgTpscQm4jsYC0AC8YNwIUdsWpYk6jgQtJ +jOBHyfSrPFulZn1D8h9gRBdV2ufks0aM7lzSFBCH5wSFPJWNcw0RLi5xtQu9F+7I +pntYlDAx7KtwBz8s1mobAqt71J46lC72/dy9XIFTY9ba4uhWN+1OwWFf3WCcxYk2 +19IW8kcNWm7tBUY/kYoqkTt7dnUsx0rmAGMxtoiw117o+A682mL9AgMBAAGjggLJ +MIICxTAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF +BQcDAgYIKwYBBQUHAwEwggJHBgNVHREEggI+MIICOqAZBgorBgEEAYI3FAIDoAsD +CQAwMTAxMDEwMaAmBgorBgEEAYI3FAIEoBgEFjQ4NjU2QzZDNkYyMDU3NkY3MjZD +NjSgJgYKKwYBBAGCNxQCBaAYExZQcmludGFibGVTdHJpbmdFeGFtcGxloCAGCisG +AQQBgjcUAgagEhQQVDYxU3RyaW5nRXhhbXBsZaAgBgorBgEEAYI3FAIHoBIWEElB +NVN0cmluZ0V4YW1wbGWgJAYKKwYBBAGCNxQCCKAWGxRHZW5lcmFsU3RyaW5nRXhh +bXBsZaBoBgorBgEEAYI3FAIJoFocWAAAAFUAAABuAAAAaQAAAHYAAABlAAAAcgAA +AHMAAABhAAAAbAAAAFMAAAB0AAAAcgAAAGkAAABuAAAAZwAAAEUAAAB4AAAAYQAA +AG0AAABwAAAAbAAAAGWgHQYKKwYBBAGCNxQCCqAPFw0yMzA2MTYxMjAwMDBaoB8G +CisGAQQBgjcUAgugERgPMjAyMzA2MTYxMjAwMDBaoCQGCisGAQQBgjcUAgygFhoU +VmlzaWJsZVN0cmluZ0V4YW1wbGWgIQYKKwYBBAGCNxQCDaATDBFVVEY4U3RyaW5n +RXhhbXBsZaAwBgorBgEEAYI3FAIOoCIeIABCAE0AUABTAHQAcgBpAG4AZwBFAHgA +YQBtAHAAbABloB4GCisGAQQBgjcUAgOgEDEODAV0ZXN0MQwFdGVzdDKgHgYKKwYB +BAGCNxQCA6AQMA4MBXRlc3QzDAV0ZXN0NDAdBgNVHQ4EFgQU/dAL0UislarYY6eq +U2dNi85py6wwHwYDVR0jBBgwFoAU5YZRBAklIKJnd3qqjq32OKSSLyAwDQYJKoZI +hvcNAQELBQADggEBAJBkQTW+26Gw/2WnMrpPzvosQaYf4EXz7040I/18PFQfTmU0 +zPl2QhDbFQsF8G6b0QuO+BHOe731nEA/He45wy1Tvd/uj00666T5wUvAhwP+5T6a +EsotvSi6mMsV0XlFjUeuj5ixa2C4yWHehoKl3F0qjjIIi/pvcaO2HfCxevibRffw +i0e4NMydOjvhwMmdXsXKmF/kJt/Wk4a7nLjOG9Pf0FQmnE13zCFKyKwJtCBUX0or +6PgcYCrq90BJSmm2mgLNSp9DqMup6H27fL4zrP1ci6/ifSAlJVl1IkyQWkysSOvS +s9eQgzbVhnyo+Rg8h+sy0dwdyj8b+V+VQYUJKd0= +-----END CERTIFICATE----- diff --git a/test/common/tls/test_data/san_multiple_othername_string_type_cert_info.h b/test/common/tls/test_data/san_multiple_othername_string_type_cert_info.h new file mode 100644 index 000000000000..ba9619f6ebed --- /dev/null +++ b/test/common/tls/test_data/san_multiple_othername_string_type_cert_info.h @@ -0,0 +1,15 @@ +#pragma once + +// NOLINT(namespace-envoy) +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_STRING_TYPE_CERT_256_HASH[] = + "b58cbd751e678a4e5dd1fc951a0b53913db3842815b55fc5331c72a14320385a"; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_STRING_TYPE_CERT_1_HASH[] = + "a69337434d38c8dc12dc723a104287870ab89098"; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_STRING_TYPE_CERT_SPKI[] = + "M85GIHaa7VOnqLYZHqTEckGgCLJbKas17MNyQF0v7/U="; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_STRING_TYPE_CERT_SERIAL[] = + "190d295e8289c13841c5827de1efc0796e442e75"; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_STRING_TYPE_CERT_NOT_BEFORE[] = + "Jun 27 21:25:51 2024 GMT"; +constexpr char TEST_SAN_MULTIPLE_OTHERNAME_STRING_TYPE_CERT_NOT_AFTER[] = + "Jun 27 21:25:51 2026 GMT"; diff --git a/test/common/tls/test_data/san_multiple_othername_string_type_key.pem b/test/common/tls/test_data/san_multiple_othername_string_type_key.pem new file mode 100644 index 000000000000..521847d929b2 --- /dev/null +++ b/test/common/tls/test_data/san_multiple_othername_string_type_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkIyDAeOKpM8eJ +YhQ8oTvDt2c2NXVX3Rwv5+DxpdzJLNuYld9ox87zOgRXHLYRz/G+3exRXqIVRAy1 +BYQfHMcLAvTYvCiRmG5CzH24ulfwCpA0xvAGcuCMVPK4PgXLdWNgTpscQm4jsYC0 +AC8YNwIUdsWpYk6jgQtJjOBHyfSrPFulZn1D8h9gRBdV2ufks0aM7lzSFBCH5wSF +PJWNcw0RLi5xtQu9F+7IpntYlDAx7KtwBz8s1mobAqt71J46lC72/dy9XIFTY9ba +4uhWN+1OwWFf3WCcxYk219IW8kcNWm7tBUY/kYoqkTt7dnUsx0rmAGMxtoiw117o ++A682mL9AgMBAAECggEAG/O6gmSjzwZZIwmPojKgC6jKXCY29U9bm0HtIiHpq13n +CMFvMjgiw3OcJRyn2fk0dVl0QvgveJkl49cMZMMBF5w5h8ZlT0Qq9Ne/ykt7qc6W +t9IwMpzyJhvaWOuBD6DOW2qPijy65nu2TBEi9Af8I/GFIF80Pq59dllWIYugTd+7 +9I2cNwrambRClPSC5wXAb7jWfaQyoKZH+Gyl8NnQNXaDpeb2OLfHRzAzVjvTB/aH +X9eSQ/QapL4H5Fnoly3B6wUike/nEHM7fYPWikw+RFXJo7JsMLOOhQjyO5lD3fuO +wwRAbsIsgntv8p8qdADXTBYdlMqUenEudQacSxg/CQKBgQDQo3kqUnNKSnqsh5kk +BDGQCsVbzWqtQGEu7yn/PHpzZMBdFlSWhFu6GZUkmPcZI5Nyh28Cwm7bEOpflNf3 +D4C6KHyDzq3+c/eG2AvE4HgCpaUglaesOy1DdSEnjXXXPxaawPKwrxOgKhWKTbnA +FGep7fI0ACEhtkX2lUxdLVQK2QKBgQDJZZDv3HdD88p0TXjV7wuo/7PSny6Fw4OA +34tlf+DUdMKdg6n+Jhe7e5RG86e6HMH1clAVYwnxV3tsdYZ5dpb+urhuBwlGZUYi +Z9aXrPE8kB5hkWXY6vk1BwOqwnlzLXaRFXwL4bYQkVipkVoqg6a+1F3aoAqSitjc +ODxgTjQaxQKBgChHJuEQwckZz8z28I2PcbIJIkiyw2FnCFvzN/xaRJl6XdiaswHL +05l9ztkd3rYvtAtsMfYqaxRHk7eYGIlNqOBHaKJZiCWTZbnWg48idoisSdCck54g +XoCjYB8upA1F1KtTjIanhfZpqXblwnJefhTEJvn6/GpxsdgEwpVKZushAoGAAZne +GhoNlKu2e1A2WrUIybImstDzJLsWK4sbZ5Ypqma3OVtXmZ6h56qm1h2PwsoBvLrI +6jKcXJ/OamFQzVxk/OdtGerSZw3dDd73dMM6M7oNk8b9IUlU69f/bncXUhQVcjfS +gaGsudr98nMmXVoolDHKATfufZW+/Zkw2a8leOUCgYBFuAZHHYBreah7yOrvETfZ +BE0UjPk1/b1c+bGNQRz0WlTYKkdeIqHNatQVcZsZQ/blzFaoMwvMZFa8ZBDyCsm0 +JXpnrqCgKYcT7Sfp/YtPmbKeDCNOGJeVTDI2Ykib/ziC8NSJ/htor5jB+HFhMdbU +1QoTB5W6OYh+uEpBnfTahA== +-----END PRIVATE KEY----- diff --git a/test/common/tls/test_data/san_othername_cert.cfg b/test/common/tls/test_data/san_othername_cert.cfg new file mode 100644 index 000000000000..192d8958a27b --- /dev/null +++ b/test/common/tls/test_data/san_othername_cert.cfg @@ -0,0 +1,36 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = US +countryName_default = US +stateOrProvinceName = California +stateOrProvinceName_default = California +localityName = San Francisco +localityName_default = San Francisco +organizationName = Lyft +organizationName_default = Lyft +organizationalUnitName = Lyft Engineering +organizationalUnitName_default = Lyft Engineering +commonName = Test Server +commonName_default = Test Server +commonName_max = 64 + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash + +[v3_ca] +basicConstraints = critical, CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +[alt_names] +otherName.1 = 1.3.6.1.4.1.311.20.2.3;UTF8:server1.example.com diff --git a/test/common/tls/test_data/san_othername_cert.pem b/test/common/tls/test_data/san_othername_cert.pem new file mode 100644 index 000000000000..fa4237a49db6 --- /dev/null +++ b/test/common/tls/test_data/san_othername_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIELDCCAxSgAwIBAgIUOdN440ZeucQGIFJ7uRJczkvWIIkwDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNjAxMjIyMTMxWhcNMjYw +NjAxMjIyMTMxWjB6MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzEUMBIGA1UEAwwLVGVzdCBTZXJ2ZXIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKnvJaESqyoF10NLh5GlMhF3Zom7D1AxiK +RIrdkHW0w8NloVSgTZvNrBQi7Iz1xfMw9Ep63qmlJGaNZcl0jf8bU8MtVP6prJNf +tBMzl+nj6sK7xYHXl7CpDwX+rg5pYovgNGdaJAFmpbteuHhqZ8Yeh5tSSLWNdjmA +c5mdl219kh5stVjMyetgjr2qaZsx99ze0syERmmE83OpSMVGtswsBv8ElPVnlEVI +qLPBpHawFjbBrCiCoxYyLGnJ3ZAnaVrZ1J4VX2PfqEj90KtSRUwuIyUe0QgTh1dX +ui56DHQWjy5lIDJl8DzeS53FZcegmYqGSyAj52+g4JAdPeQp1pyxAgMBAAGjga0w +gaowDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH +AwIGCCsGAQUFBwMBMC4GA1UdEQQnMCWgIwYKKwYBBAGCNxQCA6AVDBNzZXJ2ZXIx +LmV4YW1wbGUuY29tMB0GA1UdDgQWBBTD9LZP+nM7iX7wehpdAfg1ItRpnDAfBgNV +HSMEGDAWgBTQumAzod6zDNdAA8q1kU0sEGXwmzANBgkqhkiG9w0BAQsFAAOCAQEA +P4eYX5yZYGyOUEWVlozTazrytA/gjsVn3UXmFD/BBVCYMxzBF5VSmYFnfxHuqCjM +hkYT6aeToaEaCb0CUoQjZNun8/mTsq/m3FoZ9wTf4kdJuNk/ynlzHz5iYyGxvS0D +H5KyVRi5HHJDvi+caDov802MNi86IlT7qyQjqQ0tstgVFVnJdaaPyjlLjm6feYK5 +qs3/or6EoMowh3sWLE49RRjfN79m+1Ku+ttfyTFVR83uYGoMGHN30yFbajwo4OGV +Rmg8DCh/dZA8YZART7QM5T4gbu2SFCVvbH2+rBmJp04udHdaxLjdv4+uCejWAzID +UVEqd3E0cM8o9yEZxxVuKA== +-----END CERTIFICATE----- diff --git a/test/common/tls/test_data/san_othername_cert_info.h b/test/common/tls/test_data/san_othername_cert_info.h new file mode 100644 index 000000000000..e0632b3fe5e7 --- /dev/null +++ b/test/common/tls/test_data/san_othername_cert_info.h @@ -0,0 +1,10 @@ +#pragma once + +// NOLINT(namespace-envoy) +constexpr char TEST_SAN_OTHERNAME_CERT_256_HASH[] = + "b7d8db1c1e02cc191acc30b6a1749b83c9e14e7a0059a67ca6b5344569731e22"; +constexpr char TEST_SAN_OTHERNAME_CERT_1_HASH[] = "b0f044fd07a9554b0ecca7376e5233199ab8d781"; +constexpr char TEST_SAN_OTHERNAME_CERT_SPKI[] = "PoqTwnrbRJFeMFOcDRfMJoeYUSuZLXAlYPQl6m4R/JE="; +constexpr char TEST_SAN_OTHERNAME_CERT_SERIAL[] = "39d378e3465eb9c40620527bb9125cce4bd62089"; +constexpr char TEST_SAN_OTHERNAME_CERT_NOT_BEFORE[] = "Jun 1 22:21:31 2024 GMT"; +constexpr char TEST_SAN_OTHERNAME_CERT_NOT_AFTER[] = "Jun 1 22:21:31 2026 GMT"; diff --git a/test/common/tls/test_data/san_othername_key.pem b/test/common/tls/test_data/san_othername_key.pem new file mode 100644 index 000000000000..95599c02d8c2 --- /dev/null +++ b/test/common/tls/test_data/san_othername_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDKnvJaESqyoF10 +NLh5GlMhF3Zom7D1AxiKRIrdkHW0w8NloVSgTZvNrBQi7Iz1xfMw9Ep63qmlJGaN +Zcl0jf8bU8MtVP6prJNftBMzl+nj6sK7xYHXl7CpDwX+rg5pYovgNGdaJAFmpbte +uHhqZ8Yeh5tSSLWNdjmAc5mdl219kh5stVjMyetgjr2qaZsx99ze0syERmmE83Op +SMVGtswsBv8ElPVnlEVIqLPBpHawFjbBrCiCoxYyLGnJ3ZAnaVrZ1J4VX2PfqEj9 +0KtSRUwuIyUe0QgTh1dXui56DHQWjy5lIDJl8DzeS53FZcegmYqGSyAj52+g4JAd +PeQp1pyxAgMBAAECgf9j1P16trKncAAFIN76qr1bavpHkF2pI3nMi7EJy7yXZYyw +90WcAwjPSu7MdShauMnw5ZZSuStkXv2YcuLXAKBrkQe7u8TJk6GjJ0fI1GvVBDnW +3BlNGLdKAYPcDNj6jB2KeutiPFLpgoECQRCURwCW/NvuAbSSCXK04/M9I1kvVP3Q +ATxbgGN/wl+aUxudB5pqKX68SAWzYNCZS48Lsmmh3lHIkXC4laKDl9ykLHPVRMex +G2Ttt1IyS4ATjS7A1rZsZmy1Y7UzXjPyO5OfADK8FpTeMYHZ/3zLIn5YNlBa6AIa +7R7QtJfukfHFVEzL8t/YLQUBRZ2SuIIMR7VFI2sCgYEA+LsfH8XzPm0rRqIcEHgu +8jqfrqsuqjGQUQn7bW5YaAk4b1k3d2SWsh64XuWG60YJEKd8pEqCKFnA/EqcLuDT +b+4w1t88O67jIjjYc3BgRnvpX8Jv8IEJQsva8/N5yByDz1biuVGhKU4CQTv9Ljj2 +OAbbsNcrV1pceWNwMshNqMcCgYEA0IraZP7XmiViMr578oUHNxN3uovij6PuMmG2 +tTKXRWOFgfn28RrMONwBuovhmBvXaeo8P7fUEUMWzp8Sux6nUWnGrq6tmO2iHVSH +9EmSS5qTepttp9Vzn054k40X28tQpDRD8I3BKEby+GUhMceLlOz1I77BUnx5I7Kg +Ov4eRscCgYEA00CqiKPpmoXCEbWxvFM3HEiqQwHlGmwKNKoTv7fOol3ibsAJmf/2 +9cWdtguf8ceD/38hH7Cgp4DDpgQAbthI/HIDTRxA3jgFdZVuUW2Kd5LafZh41n3h +zbeyeSu7rTh6wuj6m4c3KAu3Yox+1nlOtfstMB8wEnsOu5K3QopZWxUCgYEAgCq2 +apfNPil3nqQ/XR+w/YJzdSz/wzQG8uPm/JnpKnYt2WIdCLVlihR843+Q2IFT9P5G +pQp/xVQsMjTFuEbwojKWL0mf23tAxTHslJCa3uhTd2kLDbk75E7AAD8YyLa+Cw0s +LC2S5wQj09GjgwqWmKLBToSwH9fsQ6pGm7sONIUCgYEArQXsPaMNSLamb0L61ppu +nOUIVWeqM2kG2dYOvNrYG8T1xcr0VCuR5tgTSkfLvaqBjOguCan+sZXxwTCcWZjC +Jj3i81Vnc9D7+E2kiu5aYZ6z1zE9YcQz5EgL4kJpJbo8qRW1Rb2cFw9LEPIYxRyI +LTPqHEfHVqwh7xFtefGtFGA= +-----END PRIVATE KEY----- diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index c179dd256411..c7a74df4c2a1 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -464,6 +464,7 @@ UDP UDS UHV UNC +UPN URI URL USEVC From 183e8b44bd6ca672001605445887752137e51ef3 Mon Sep 17 00:00:00 2001 From: Kuo-Chung Hsu Date: Thu, 27 Jun 2024 17:45:30 -0700 Subject: [PATCH 14/31] thrift: save one comparison of thrift compact protocol (#34736) Risk Level: low Testing: existing tests Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: kuochunghsu --- .../thrift_proxy/compact_protocol_impl.cc | 22 +++++++------------ .../thrift_proxy/compact_protocol_impl.h | 2 -- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.cc b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.cc index b17c639ff9ee..c41a78d38ae5 100644 --- a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.cc @@ -112,24 +112,15 @@ bool CompactProtocolImpl::peekReplyPayload(Buffer::Instance& buffer, ReplyType& return false; } - validateFieldId(id); + if (id < std::numeric_limits::min() || id > std::numeric_limits::max()) { + throw EnvoyException(absl::StrCat("invalid compact protocol field id ", id)); + } + // successful response struct in field id 0, error (IDL exception) in field id greater than 0 reply_type = id == 0 ? ReplyType::Success : ReplyType::Error; return true; } -void CompactProtocolImpl::validateFieldId(int32_t id) { - if (id >= 0 && id <= std::numeric_limits::max()) { - return; - } - - if (id < 0 && id >= std::numeric_limits::min()) { - return; - } - - throw EnvoyException(absl::StrCat("invalid compact protocol field id ", id)); -} - bool CompactProtocolImpl::readStructBegin(Buffer::Instance& buffer, std::string& name) { UNREFERENCED_PARAMETER(buffer); name.clear(); // compact protocol does not transmit struct names @@ -187,7 +178,10 @@ bool CompactProtocolImpl::readFieldBegin(Buffer::Instance& buffer, std::string& return false; } - validateFieldId(id); + if (id < std::numeric_limits::min() || id > std::numeric_limits::max()) { + throw EnvoyException(absl::StrCat("invalid compact protocol field id ", id)); + } + compact_field_type = static_cast(delta_and_type); compact_field_id = static_cast(id); } else { diff --git a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h index f7435a662243..a2c978bcf2f8 100644 --- a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h +++ b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h @@ -98,8 +98,6 @@ class CompactProtocolImpl : public Protocol { void writeFieldBeginInternal(Buffer::Instance& buffer, FieldType field_type, int16_t field_id, absl::optional field_type_override); - static void validateFieldId(int32_t id); - std::stack last_field_id_stack_{}; int16_t last_field_id_{0}; From 9ce333ba1c46cde9e1a7af5bef99aceea1a80d77 Mon Sep 17 00:00:00 2001 From: Tianyu <72890320+tyxia@users.noreply.github.com> Date: Fri, 28 Jun 2024 00:17:08 -0400 Subject: [PATCH 15/31] gRPC: Add support to control envoy generated headers (#34179) This PR provides gRPC client level control over Envoy generated headers. It currently controls x-envoy-internal and x-forwarded-for (can be expanded if needed) If false, header will be added. But it can be overridden by setting setSendInternal or setSendXff to false in Http::AsyncClient::StreamOptions, as per stream control. If true, header will be removed and can not be overridden by per stream option. This logic is designed in this way because: Preserve backwards compatible behavior: Both headers are still sent by default If any existing users remove them with StreamOptions, headers are still removed Still provide the per stream override control Override here implicitly means setting to false as their default value in AsyncClient::StreamOptions is true. Thus, per stream override is still available, just in one-way direction: disable on per stream basis The only thing is that now user can not set StreamOptions to true if they are disabled in config. But it should be fine because: For existing user, no one should set them to true in StreamOptions as they are already default to true. For future user, per stream control can still be achieved as stated above. Signed-off-by: Tianyu Xia --- api/envoy/config/core/v3/grpc_service.proto | 7 +++++ source/common/grpc/async_client_impl.cc | 13 +++++++- source/common/grpc/async_client_impl.h | 1 + .../grpc/grpc_client_integration_test.cc | 31 +++++++++++++++++++ .../grpc_client_integration_test_harness.h | 23 ++++++++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/api/envoy/config/core/v3/grpc_service.proto b/api/envoy/config/core/v3/grpc_service.proto index 471f9165b9d1..5fd7921a8062 100644 --- a/api/envoy/config/core/v3/grpc_service.proto +++ b/api/envoy/config/core/v3/grpc_service.proto @@ -29,6 +29,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; message GrpcService { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.GrpcService"; + // [#next-free-field: 6] message EnvoyGrpc { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.GrpcService.EnvoyGrpc"; @@ -55,6 +56,12 @@ message GrpcService { // This limit is applied to individual messages in the streaming response and not the total size of streaming response. // Defaults to 0, which means unlimited. google.protobuf.UInt32Value max_receive_message_length = 4; + + // This provides gRPC client level control over envoy generated headers. + // If false, the header will be sent but it can be overridden by per stream option. + // If true, the header will be removed and can not be overridden by per stream option. + // Default to false. + bool skip_envoy_headers = 5; } // [#next-free-field: 9] diff --git a/source/common/grpc/async_client_impl.cc b/source/common/grpc/async_client_impl.cc index dd89bc7bec97..43f32db72bdb 100644 --- a/source/common/grpc/async_client_impl.cc +++ b/source/common/grpc/async_client_impl.cc @@ -19,7 +19,8 @@ AsyncClientImpl::AsyncClientImpl(Upstream::ClusterManager& cm, TimeSource& time_source) : max_recv_message_length_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.envoy_grpc(), max_receive_message_length, 0)), - cm_(cm), remote_cluster_name_(config.envoy_grpc().cluster_name()), + skip_envoy_headers_(config.envoy_grpc().skip_envoy_headers()), cm_(cm), + remote_cluster_name_(config.envoy_grpc().cluster_name()), host_name_(config.envoy_grpc().authority()), time_source_(time_source), metadata_parser_(THROW_OR_RETURN_VALUE( Router::HeaderParser::configure( @@ -85,6 +86,16 @@ AsyncStreamImpl::AsyncStreamImpl(AsyncClientImpl& parent, absl::string_view serv if (!options.retry_policy.has_value() && parent_.retryPolicy().has_value()) { options_.setRetryPolicy(*parent_.retryPolicy()); } + + // Apply parent `skip_envoy_headers_` setting from configuration, if no per-stream + // override. (i.e., no override of default stream option from true to false) + if (options.send_internal) { + options_.setSendInternal(!parent_.skip_envoy_headers_); + } + if (options.send_xff) { + options_.setSendXff(!parent_.skip_envoy_headers_); + } + // Configure the maximum frame length decoder_.setMaxFrameLength(parent_.max_recv_message_length_); diff --git a/source/common/grpc/async_client_impl.h b/source/common/grpc/async_client_impl.h index b088f34dc637..3788e6e0ab04 100644 --- a/source/common/grpc/async_client_impl.h +++ b/source/common/grpc/async_client_impl.h @@ -44,6 +44,7 @@ class AsyncClientImpl final : public RawAsyncClient { private: const uint32_t max_recv_message_length_; + const bool skip_envoy_headers_; Upstream::ClusterManager& cm_; const std::string remote_cluster_name_; // The host header value in the http transport. diff --git a/test/common/grpc/grpc_client_integration_test.cc b/test/common/grpc/grpc_client_integration_test.cc index 5d346e3161e5..5d5d6ad4b075 100644 --- a/test/common/grpc/grpc_client_integration_test.cc +++ b/test/common/grpc/grpc_client_integration_test.cc @@ -64,6 +64,37 @@ TEST_P(GrpcClientIntegrationTest, BasicStream) { dispatcher_helper_.runDispatcher(); } +// A simple request-reply stream, "x-envoy-internal" and `x-forward-for` headers +// are removed due to grpc service configuration. +TEST_P(GrpcClientIntegrationTest, BasicStreamRemoveInternalHeaders) { + // "x-envoy-internal" and `x-forward-for` headers are only available on Envoy gRPC path. + SKIP_IF_GRPC_CLIENT(ClientType::GoogleGrpc); + skip_envoy_headers_ = true; + initialize(); + auto stream = createStream(empty_metadata_); + stream->sendRequest(); + stream->sendServerInitialMetadata(empty_metadata_); + stream->sendReply(); + stream->sendServerTrailers(Status::WellKnownGrpcStatus::Ok, "", empty_metadata_); + dispatcher_helper_.runDispatcher(); +} + +// A simple request-reply stream, "x-envoy-internal" and `x-forward-for` headers +// are removed due to per stream options which overrides the gRPC service configuration. +TEST_P(GrpcClientIntegrationTest, BasicStreamRemoveInternalHeadersWithStreamOption) { + // "x-envoy-internal" and `x-forward-for` headers are only available on Envoy gRPC path. + SKIP_IF_GRPC_CLIENT(ClientType::GoogleGrpc); + send_internal_header_stream_option_ = false; + send_xff_header_stream_option_ = false; + initialize(); + auto stream = createStream(empty_metadata_); + stream->sendRequest(); + stream->sendServerInitialMetadata(empty_metadata_); + stream->sendReply(); + stream->sendServerTrailers(Status::WellKnownGrpcStatus::Ok, "", empty_metadata_); + dispatcher_helper_.runDispatcher(); +} + // Validate that a simple request-reply stream works with bytes metering in Envoy gRPC. TEST_P(GrpcClientIntegrationTest, BasicStreamWithBytesMeter) { // Currently, only Envoy gRPC's bytes metering is based on the bytes meter in Envoy. diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index ea010c25867c..556d68909c4e 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -357,6 +357,8 @@ template class GrpcClientIntegrationTestBase { envoy_grpc_max_recv_msg_length); } + config.mutable_envoy_grpc()->set_skip_envoy_headers(skip_envoy_headers_); + fillServiceWideInitialMetadata(config); return std::make_unique(cm_, config, dispatcher_->timeSource()); } @@ -394,6 +396,22 @@ template class GrpcClientIntegrationTestBase { EXPECT_EQ("/helloworld.Greeter/SayHello", stream_headers_->get_(":path")); EXPECT_EQ("application/grpc", stream_headers_->get_("content-type")); EXPECT_EQ("trailers", stream_headers_->get_("te")); + + // "x-envoy-internal" and `x-forward-for` headers are only available in envoy gRPC path. + // They will be removed when either envoy gRPC config or stream option is false. + if (getClientType() == ClientType::EnvoyGrpc) { + if (!skip_envoy_headers_ && send_internal_header_stream_option_) { + EXPECT_FALSE(stream_headers_->get_("x-envoy-internal").empty()); + } else { + EXPECT_TRUE(stream_headers_->get_("x-envoy-internal").empty()); + } + if (!skip_envoy_headers_ && send_xff_header_stream_option_) { + EXPECT_FALSE(stream_headers_->get_("x-forwarded-for").empty()); + } else { + EXPECT_TRUE(stream_headers_->get_("x-forwarded-for").empty()); + } + } + for (const auto& value : initial_metadata) { EXPECT_EQ(value.second, stream_headers_->get_(value.first)); } @@ -471,6 +489,8 @@ template class GrpcClientIntegrationTestBase { envoy::config::core::v3::Metadata m; (*m.mutable_filter_metadata())["com.foo.bar"] = {}; options.setMetadata(m); + options.setSendInternal(send_internal_header_stream_option_); + options.setSendXff(send_xff_header_stream_option_); stream->grpc_stream_ = grpc_client_->start(*method_descriptor_, *stream, options); EXPECT_NE(stream->grpc_stream_, nullptr); @@ -533,6 +553,9 @@ template class GrpcClientIntegrationTestBase { Router::MockShadowWriter* mock_shadow_writer_ = new Router::MockShadowWriter(); Router::ShadowWriterPtr shadow_writer_ptr_{mock_shadow_writer_}; Network::ClientConnectionPtr client_connection_; + bool skip_envoy_headers_{false}; + bool send_internal_header_stream_option_{true}; + bool send_xff_header_stream_option_{true}; }; // The integration test for Envoy gRPC and Google gRPC. It uses `TestRealTimeSystem`. From 3feff04eeb2356cf16f799bc9fb812e5b765315f Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Fri, 28 Jun 2024 00:49:27 -0400 Subject: [PATCH 16/31] xds-failover: plumbing the API and adding integration test (#34761) Adding the ability to use xDS-Failover into the API (ADS config). Introducing the xds-failover integration tests. This is all guarded behind the envoy.restart_features.xds_failover_support guard. In the future, once we test xDS-Failover out and ensure it works as expected, the runtime guard will be removed. Risk Level: low if not configured, medium if it is. Testing: Added unit tests and integration tests Docs Changes: N/A (once xDS-failover behavior will be finalized). Release Notes: N/A (once xDS-failover behavior will be finalized). Platform Specific Features: N/A. Signed-off-by: Adi Suissa-Peleg --- source/common/config/utility.cc | 59 +- source/common/config/utility.h | 7 +- .../common/upstream/cluster_manager_impl.cc | 61 +- .../grpc_collection_subscription_factory.cc | 10 +- .../grpc/grpc_mux_failover.h | 2 + .../config_subscription/grpc/grpc_mux_impl.cc | 24 +- .../grpc/grpc_subscription_factory.cc | 20 +- .../grpc/new_grpc_mux_impl.cc | 25 +- .../grpc/xds_mux/grpc_mux_impl.cc | 16 +- source/server/server.cc | 3 +- test/common/config/BUILD | 1 + test/common/config/utility_test.cc | 94 ++- .../common/subscription_factory_impl_test.cc | 85 ++- .../extensions/config_subscription/grpc/BUILD | 18 + .../grpc/xds_failover_integration_test.cc | 575 ++++++++++++++++++ test/integration/base_integration_test.h | 2 +- 16 files changed, 929 insertions(+), 73 deletions(-) create mode 100644 test/extensions/config_subscription/grpc/xds_failover_integration_test.cc diff --git a/source/common/config/utility.cc b/source/common/config/utility.cc index 963def83d91b..50a058aa7961 100644 --- a/source/common/config/utility.cc +++ b/source/common/config/utility.cc @@ -12,6 +12,8 @@ #include "source/common/common/assert.h" #include "source/common/protobuf/utility.h" +#include "absl/status/status.h" + namespace Envoy { namespace Config { @@ -68,11 +70,13 @@ namespace { /** * Check the grpc_services and cluster_names for API config sanity. Throws on error. * @param api_config_source the config source to validate. + * @param max_grpc_services the maximal number of grpc services allowed. * @return an invalid status when an API config has the wrong number of gRPC * services or cluster names, depending on expectations set by its API type. */ absl::Status -checkApiConfigSourceNames(const envoy::config::core::v3::ApiConfigSource& api_config_source) { +checkApiConfigSourceNames(const envoy::config::core::v3::ApiConfigSource& api_config_source, + int max_grpc_services) { const bool is_grpc = (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC || api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC); @@ -89,10 +93,10 @@ checkApiConfigSourceNames(const envoy::config::core::v3::ApiConfigSource& api_co fmt::format("{}::(DELTA_)GRPC must not have a cluster name specified: {}", api_config_source.GetTypeName(), api_config_source.DebugString())); } - if (api_config_source.grpc_services().size() > 1) { - return absl::InvalidArgumentError( - fmt::format("{}::(DELTA_)GRPC must have a single gRPC service specified: {}", - api_config_source.GetTypeName(), api_config_source.DebugString())); + if (api_config_source.grpc_services_size() > max_grpc_services) { + return absl::InvalidArgumentError(fmt::format( + "{}::(DELTA_)GRPC must have no more than {} gRPC services specified: {}", + api_config_source.GetTypeName(), max_grpc_services, api_config_source.DebugString())); } } else { if (!api_config_source.grpc_services().empty()) { @@ -133,7 +137,9 @@ absl::Status Utility::checkApiConfigSourceSubscriptionBackingCluster( envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC) { return absl::OkStatus(); } - RETURN_IF_NOT_OK(checkApiConfigSourceNames(api_config_source)); + RETURN_IF_NOT_OK(checkApiConfigSourceNames( + api_config_source, + Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support") ? 2 : 1)); const bool is_grpc = (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC); @@ -153,6 +159,14 @@ absl::Status Utility::checkApiConfigSourceSubscriptionBackingCluster( primary_clusters, api_config_source.grpc_services()[0].envoy_grpc().cluster_name(), api_config_source.GetTypeName())); } + if (Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support") && + api_config_source.grpc_services_size() == 2 && + api_config_source.grpc_services()[1].has_envoy_grpc()) { + // If an Envoy failover gRPC exists, we validate its cluster name. + RETURN_IF_NOT_OK(Utility::validateClusterName( + primary_clusters, api_config_source.grpc_services()[1].envoy_grpc().cluster_name(), + api_config_source.GetTypeName())); + } } // Otherwise, there is no cluster name to validate. return absl::OkStatus(); @@ -161,15 +175,23 @@ absl::Status Utility::checkApiConfigSourceSubscriptionBackingCluster( absl::optional Utility::getGrpcControlPlane(const envoy::config::core::v3::ApiConfigSource& api_config_source) { if (api_config_source.grpc_services_size() > 0) { - // Only checking for the first entry in grpc_services, because Envoy's xDS implementation - // currently only considers the first gRPC endpoint and ignores any other xDS management servers - // specified in an ApiConfigSource. + std::string res = ""; + // In case more than one grpc service is defined, concatenate the names for + // a unique GrpcControlPlane identifier. if (api_config_source.grpc_services(0).has_envoy_grpc()) { - return api_config_source.grpc_services(0).envoy_grpc().cluster_name(); + res = api_config_source.grpc_services(0).envoy_grpc().cluster_name(); + } else if (api_config_source.grpc_services(0).has_google_grpc()) { + res = api_config_source.grpc_services(0).google_grpc().target_uri(); } - if (api_config_source.grpc_services(0).has_google_grpc()) { - return api_config_source.grpc_services(0).google_grpc().target_uri(); + // Concatenate the failover gRPC service. + if (api_config_source.grpc_services_size() == 2) { + if (api_config_source.grpc_services(1).has_envoy_grpc()) { + absl::StrAppend(&res, ",", api_config_source.grpc_services(1).envoy_grpc().cluster_name()); + } else if (api_config_source.grpc_services(1).has_google_grpc()) { + absl::StrAppend(&res, ",", api_config_source.grpc_services(1).google_grpc().target_uri()); + } } + return res; } return absl::nullopt; } @@ -204,8 +226,10 @@ Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& absl::StatusOr Utility::factoryForGrpcApiConfigSource( Grpc::AsyncClientManager& async_client_manager, const envoy::config::core::v3::ApiConfigSource& api_config_source, Stats::Scope& scope, - bool skip_cluster_check) { - RETURN_IF_NOT_OK(checkApiConfigSourceNames(api_config_source)); + bool skip_cluster_check, int grpc_service_idx) { + RETURN_IF_NOT_OK(checkApiConfigSourceNames( + api_config_source, + Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support") ? 2 : 1)); if (api_config_source.api_type() != envoy::config::core::v3::ApiConfigSource::GRPC && api_config_source.api_type() != envoy::config::core::v3::ApiConfigSource::DELTA_GRPC) { @@ -214,8 +238,13 @@ absl::StatusOr Utility::factoryForGrpcApiConfigSour api_config_source.DebugString())); } + if (grpc_service_idx >= api_config_source.grpc_services_size()) { + // No returned factory in case there's no entry. + return nullptr; + } + envoy::config::core::v3::GrpcService grpc_service; - grpc_service.MergeFrom(api_config_source.grpc_services(0)); + grpc_service.MergeFrom(api_config_source.grpc_services(grpc_service_idx)); return async_client_manager.factoryForGrpcService(grpc_service, scope, skip_cluster_check); } diff --git a/source/common/config/utility.h b/source/common/config/utility.h index 25b4eaaccfa9..1faadb15f3e0 100644 --- a/source/common/config/utility.h +++ b/source/common/config/utility.h @@ -391,12 +391,15 @@ class Utility { * @param async_client_manager gRPC async client manager. * @param api_config_source envoy::config::core::v3::ApiConfigSource. Must have config type GRPC. * @param skip_cluster_check whether to skip cluster validation. - * @return Grpc::AsyncClientFactoryPtr gRPC async client factory. + * @param grpc_service_idx index of the grpc service in the api_config_source. If there's no entry + * in the given index, a nullptr factory will be returned. + * @return Grpc::AsyncClientFactoryPtr gRPC async client factory, or nullptr if there's no + * grpc_service in the given index. */ static absl::StatusOr factoryForGrpcApiConfigSource(Grpc::AsyncClientManager& async_client_manager, const envoy::config::core::v3::ApiConfigSource& api_config_source, - Stats::Scope& scope, bool skip_cluster_check); + Stats::Scope& scope, bool skip_cluster_check, int grpc_service_idx); /** * Translate opaque config from google.protobuf.Any to defined proto message. diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 122d66c2010a..44fa2498ec9b 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -88,16 +88,25 @@ getOrigin(const Network::TransportSocketOptionsConstSharedPtr& options, HostCons bool isBlockingAdsCluster(const envoy::config::bootstrap::v3::Bootstrap& bootstrap, absl::string_view cluster_name) { + bool blocking_ads_cluster = false; if (bootstrap.dynamic_resources().has_ads_config()) { const auto& ads_config_source = bootstrap.dynamic_resources().ads_config(); // We only care about EnvoyGrpc, not GoogleGrpc, because we only need to delay ADS mux // initialization if it uses an Envoy cluster that needs to be initialized first. We don't // depend on the same cluster initialization when opening a gRPC stream for GoogleGrpc. - return (ads_config_source.grpc_services_size() > 0 && - ads_config_source.grpc_services(0).has_envoy_grpc() && - ads_config_source.grpc_services(0).envoy_grpc().cluster_name() == cluster_name); + blocking_ads_cluster = + (ads_config_source.grpc_services_size() > 0 && + ads_config_source.grpc_services(0).has_envoy_grpc() && + ads_config_source.grpc_services(0).envoy_grpc().cluster_name() == cluster_name); + if (Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support")) { + // Validate the failover server if there is one. + blocking_ads_cluster |= + (ads_config_source.grpc_services_size() == 2 && + ads_config_source.grpc_services(1).has_envoy_grpc() && + ads_config_source.grpc_services(1).envoy_grpc().cluster_name() == cluster_name); + } } - return false; + return blocking_ads_cluster; } } // namespace @@ -447,14 +456,22 @@ ClusterManagerImpl::initialize(const envoy::config::bootstrap::v3::Bootstrap& bo if (!factory) { return absl::InvalidArgumentError(fmt::format("{} not found", name)); } - auto factory_or_error = Config::Utility::factoryForGrpcApiConfigSource( - *async_client_manager_, dyn_resources.ads_config(), *stats_.rootScope(), false); - RETURN_IF_STATUS_NOT_OK(factory_or_error); - ads_mux_ = factory->create(factory_or_error.value()->createUncachedRawAsyncClient(), nullptr, - dispatcher_, random_, *stats_.rootScope(), - dyn_resources.ads_config(), local_info_, - std::move(custom_config_validators), std::move(backoff_strategy), - makeOptRefFromPtr(xds_config_tracker_.get()), {}, use_eds_cache); + auto factory_primary_or_error = Config::Utility::factoryForGrpcApiConfigSource( + *async_client_manager_, dyn_resources.ads_config(), *stats_.rootScope(), false, 0); + RETURN_IF_STATUS_NOT_OK(factory_primary_or_error); + Grpc::AsyncClientFactoryPtr factory_failover = nullptr; + if (Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support")) { + auto factory_failover_or_error = Config::Utility::factoryForGrpcApiConfigSource( + *async_client_manager_, dyn_resources.ads_config(), *stats_.rootScope(), false, 1); + RETURN_IF_STATUS_NOT_OK(factory_failover_or_error); + factory_failover = std::move(factory_failover_or_error.value()); + } + ads_mux_ = factory->create( + factory_primary_or_error.value()->createUncachedRawAsyncClient(), + factory_failover ? factory_failover->createUncachedRawAsyncClient() : nullptr, + dispatcher_, random_, *stats_.rootScope(), dyn_resources.ads_config(), local_info_, + std::move(custom_config_validators), std::move(backoff_strategy), + makeOptRefFromPtr(xds_config_tracker_.get()), {}, use_eds_cache); } else { absl::Status status = Config::Utility::checkTransportVersion(dyn_resources.ads_config()); RETURN_IF_NOT_OK(status); @@ -470,12 +487,20 @@ ClusterManagerImpl::initialize(const envoy::config::bootstrap::v3::Bootstrap& bo if (!factory) { return absl::InvalidArgumentError(fmt::format("{} not found", name)); } - auto factory_or_error = Config::Utility::factoryForGrpcApiConfigSource( - *async_client_manager_, dyn_resources.ads_config(), *stats_.rootScope(), false); - RETURN_IF_STATUS_NOT_OK(factory_or_error); + auto factory_primary_or_error = Config::Utility::factoryForGrpcApiConfigSource( + *async_client_manager_, dyn_resources.ads_config(), *stats_.rootScope(), false, 0); + RETURN_IF_STATUS_NOT_OK(factory_primary_or_error); + Grpc::AsyncClientFactoryPtr factory_failover = nullptr; + if (Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support")) { + auto factory_failover_or_error = Config::Utility::factoryForGrpcApiConfigSource( + *async_client_manager_, dyn_resources.ads_config(), *stats_.rootScope(), false, 1); + RETURN_IF_STATUS_NOT_OK(factory_failover_or_error); + factory_failover = std::move(factory_failover_or_error.value()); + } ads_mux_ = factory->create( - factory_or_error.value()->createUncachedRawAsyncClient(), nullptr, dispatcher_, random_, - *stats_.rootScope(), dyn_resources.ads_config(), local_info_, + factory_primary_or_error.value()->createUncachedRawAsyncClient(), + factory_failover ? factory_failover->createUncachedRawAsyncClient() : nullptr, + dispatcher_, random_, *stats_.rootScope(), dyn_resources.ads_config(), local_info_, std::move(custom_config_validators), std::move(backoff_strategy), makeOptRefFromPtr(xds_config_tracker_.get()), xds_delegate_opt_ref, use_eds_cache); } @@ -568,7 +593,7 @@ absl::Status ClusterManagerImpl::initializeSecondaryClusters( absl::Status status = Config::Utility::checkTransportVersion(load_stats_config); RETURN_IF_NOT_OK(status); auto factory_or_error = Config::Utility::factoryForGrpcApiConfigSource( - *async_client_manager_, load_stats_config, *stats_.rootScope(), false); + *async_client_manager_, load_stats_config, *stats_.rootScope(), false, 0); RETURN_IF_STATUS_NOT_OK(factory_or_error); load_stats_reporter_ = std::make_unique( local_info_, *this, *stats_.rootScope(), diff --git a/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc b/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc index 5b8433fd989c..9468f1d32c2e 100644 --- a/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc +++ b/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc @@ -23,15 +23,15 @@ SubscriptionPtr DeltaGrpcCollectionConfigSubscriptionFactory::create( THROW_IF_STATUS_NOT_OK(strategy_or_error, throw); JitteredExponentialBackOffStrategyPtr backoff_strategy = std::move(strategy_or_error.value()); - auto factory_or_error = Config::Utility::factoryForGrpcApiConfigSource( - data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true); - THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + auto factory_primary_or_error = Config::Utility::factoryForGrpcApiConfigSource( + data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true, 0); + THROW_IF_STATUS_NOT_OK(factory_primary_or_error, throw); absl::StatusOr rate_limit_settings_or_error = Utility::parseRateLimitSettings(api_config_source); THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ - factory_or_error.value()->createUncachedRawAsyncClient(), - /*failover_async_client_*/ nullptr, + factory_primary_or_error.value()->createUncachedRawAsyncClient(), + /*failover_async_client_=*/nullptr, /*dispatcher_=*/data.dispatcher_, /*service_method_=*/deltaGrpcMethod(data.type_url_), /*local_info_=*/data.local_info_, diff --git a/source/extensions/config_subscription/grpc/grpc_mux_failover.h b/source/extensions/config_subscription/grpc/grpc_mux_failover.h index 60cba40cbd18..00876af5c555 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_failover.h +++ b/source/extensions/config_subscription/grpc/grpc_mux_failover.h @@ -50,6 +50,8 @@ template class GrpcMuxFailover : public GrpcStreamInterface, public Logger::Loggable { public: + static constexpr uint32_t DefaultFailoverBackoffMilliseconds = 500; + // A GrpcStream creator function that receives the stream callbacks and returns a // GrpcStream object. This is introduced to facilitate dependency injection for // testing and will be used to create the primary and failover streams. diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc index 8091835caa6e..f402fafb8878 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc @@ -97,8 +97,28 @@ GrpcMuxImpl::createGrpcStreamObject(GrpcMuxContext& grpc_mux_context) { grpc_mux_context.rate_limit_settings_); }, /*failover_stream_creator=*/ - // TODO(adisuissa): implement when failover is fully plumbed. - absl::nullopt, + grpc_mux_context.failover_async_client_ + ? absl::make_optional( + [&grpc_mux_context]( + GrpcStreamCallbacks* + callbacks) + -> GrpcStreamInterfacePtr { + return std::make_unique< + GrpcStream>( + callbacks, std::move(grpc_mux_context.failover_async_client_), + grpc_mux_context.service_method_, grpc_mux_context.dispatcher_, + grpc_mux_context.scope_, + // TODO(adisuissa): the backoff strategy for the failover should + // be the same as the primary source. + std::make_unique( + GrpcMuxFailover:: + DefaultFailoverBackoffMilliseconds), + grpc_mux_context.rate_limit_settings_); + }) + : absl::nullopt, /*grpc_mux_callbacks=*/*this, /*dispatch=*/grpc_mux_context.dispatcher_); } diff --git a/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc b/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc index e3792b49b42f..da174b438ce7 100644 --- a/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc +++ b/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc @@ -26,15 +26,15 @@ GrpcConfigSubscriptionFactory::create(ConfigSubscriptionFactory::SubscriptionDat THROW_IF_STATUS_NOT_OK(strategy_or_error, throw); JitteredExponentialBackOffStrategyPtr backoff_strategy = std::move(strategy_or_error.value()); - auto factory_or_error = Utility::factoryForGrpcApiConfigSource( - data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true); - THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + auto factory_primary_or_error = Utility::factoryForGrpcApiConfigSource( + data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true, 0); + THROW_IF_STATUS_NOT_OK(factory_primary_or_error, throw); absl::StatusOr rate_limit_settings_or_error = Utility::parseRateLimitSettings(api_config_source); THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ - /*async_client_=*/factory_or_error.value()->createUncachedRawAsyncClient(), - /*failover_async_client_=*/nullptr, + /*async_client_=*/factory_primary_or_error.value()->createUncachedRawAsyncClient(), + /*failover_async_client_=*/nullptr, // Failover is only supported for ADS. /*dispatcher_=*/data.dispatcher_, /*service_method_=*/sotwGrpcMethod(data.type_url_), /*local_info_=*/data.local_info_, @@ -75,15 +75,15 @@ DeltaGrpcConfigSubscriptionFactory::create(ConfigSubscriptionFactory::Subscripti THROW_IF_STATUS_NOT_OK(strategy_or_error, throw); JitteredExponentialBackOffStrategyPtr backoff_strategy = std::move(strategy_or_error.value()); - auto factory_or_error = Utility::factoryForGrpcApiConfigSource( - data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true); - THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + auto factory_primary_or_error = Utility::factoryForGrpcApiConfigSource( + data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true, 0); + THROW_IF_STATUS_NOT_OK(factory_primary_or_error, throw); absl::StatusOr rate_limit_settings_or_error = Utility::parseRateLimitSettings(api_config_source); THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ - /*async_client_=*/factory_or_error.value()->createUncachedRawAsyncClient(), - /*failover_async_client_=*/nullptr, + /*async_client_=*/factory_primary_or_error.value()->createUncachedRawAsyncClient(), + /*failover_async_client_=*/nullptr, // Failover is only supported for ADS. /*dispatcher_=*/data.dispatcher_, /*service_method_=*/deltaGrpcMethod(data.type_url_), /*local_info_=*/data.local_info_, diff --git a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc index c06685f13af6..44e64c1ca43e 100644 --- a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc @@ -71,8 +71,29 @@ NewGrpcMuxImpl::createGrpcStreamObject(GrpcMuxContext& grpc_mux_context) { grpc_mux_context.rate_limit_settings_); }, /*failover_stream_creator=*/ - // TODO(adisuissa): implement when failover is fully plumbed. - absl::nullopt, + grpc_mux_context.failover_async_client_ + ? absl::make_optional( + [&grpc_mux_context]( + GrpcStreamCallbacks* + callbacks) + -> GrpcStreamInterfacePtr< + envoy::service::discovery::v3::DeltaDiscoveryRequest, + envoy::service::discovery::v3::DeltaDiscoveryResponse> { + return std::make_unique< + GrpcStream>( + callbacks, std::move(grpc_mux_context.failover_async_client_), + grpc_mux_context.service_method_, grpc_mux_context.dispatcher_, + grpc_mux_context.scope_, + // TODO(adisuissa): the backoff strategy for the failover should + // be the same as the primary source. + std::make_unique( + GrpcMuxFailover:: + DefaultFailoverBackoffMilliseconds), + grpc_mux_context.rate_limit_settings_); + }) + : absl::nullopt, /*grpc_mux_callbacks=*/*this, /*dispatch=*/grpc_mux_context.dispatcher_); } diff --git a/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc index b564b0b929db..ef39566a8383 100644 --- a/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc @@ -72,8 +72,20 @@ GrpcMuxImpl::createGrpcStreamObject(GrpcMuxContext& grpc_mux_conte grpc_mux_context.rate_limit_settings_); }, /*failover_stream_creator=*/ - // TODO(adisuissa): implement when failover is fully plumbed. - absl::nullopt, + grpc_mux_context.failover_async_client_ + ? absl::make_optional([&grpc_mux_context](GrpcStreamCallbacks* callbacks) + -> GrpcStreamInterfacePtr { + return std::make_unique>( + callbacks, std::move(grpc_mux_context.failover_async_client_), + grpc_mux_context.service_method_, grpc_mux_context.dispatcher_, + grpc_mux_context.scope_, + // TODO(adisuissa): the backoff strategy for the failover should + // be the same as the primary source. + std::make_unique( + GrpcMuxFailover::DefaultFailoverBackoffMilliseconds), + grpc_mux_context.rate_limit_settings_); + }) + : absl::nullopt, /*grpc_mux_callbacks=*/*this, /*dispatch=*/grpc_mux_context.dispatcher_); } diff --git a/source/server/server.cc b/source/server/server.cc index 54d2ac257a1b..f8bef3157697 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -818,8 +818,9 @@ void InstanceBase::onRuntimeReady() { bootstrap_.grpc_async_client_manager_config()); TRY_ASSERT_MAIN_THREAD { THROW_IF_NOT_OK(Config::Utility::checkTransportVersion(hds_config)); + // HDS does not support xDS-Failover. auto factory_or_error = Config::Utility::factoryForGrpcApiConfigSource( - *async_client_manager_, hds_config, *stats_store_.rootScope(), false); + *async_client_manager_, hds_config, *stats_store_.rootScope(), false, 0); THROW_IF_STATUS_NOT_OK(factory_or_error, throw); hds_delegate_ = std::make_unique( serverFactoryContext(), *stats_store_.rootScope(), diff --git a/test/common/config/BUILD b/test/common/config/BUILD index b11e527d8c7b..b72678b0089e 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -137,6 +137,7 @@ envoy_cc_test( "//test/mocks/upstream:thread_local_cluster_mocks", "//test/test_common:environment_lib", "//test/test_common:logging_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@com_github_cncf_xds//udpa/type/v1:pkg_cc_proto", "@com_github_cncf_xds//xds/type/v3:pkg_cc_proto", diff --git a/test/common/config/utility_test.cc b/test/common/config/utility_test.cc index 72d7864f1731..b3945df1e62d 100644 --- a/test/common/config/utility_test.cc +++ b/test/common/config/utility_test.cc @@ -20,6 +20,7 @@ #include "test/mocks/upstream/thread_local_cluster.h" #include "test/test_common/environment.h" #include "test/test_common/logging.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "absl/types/optional.h" @@ -137,7 +138,7 @@ TEST(UtilityTest, FactoryForGrpcApiConfigSource) { envoy::config::core::v3::ApiConfigSource api_config_source; api_config_source.set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); EXPECT_THAT(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, - scope, false) + scope, false, 0) .status() .message(), HasSubstr("API configs must have either a gRPC service or a cluster name defined")); @@ -149,12 +150,12 @@ TEST(UtilityTest, FactoryForGrpcApiConfigSource) { api_config_source.add_grpc_services(); api_config_source.add_grpc_services(); EXPECT_THAT(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, - scope, false) + scope, false, 0) .status() .message(), - ContainsRegex(fmt::format("{}::.DELTA_.GRPC must have a single gRPC service " - "specified:", - api_config_source.GetTypeName()))); + ContainsRegex(fmt::format( + "{}::.DELTA_.GRPC must have no more than 1 gRPC services specified:", + api_config_source.GetTypeName()))); } { @@ -163,7 +164,7 @@ TEST(UtilityTest, FactoryForGrpcApiConfigSource) { api_config_source.add_cluster_names(); // this also logs a warning for setting REST cluster names for a gRPC API config. EXPECT_THAT(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, - scope, false) + scope, false, 0) .status() .message(), ContainsRegex(fmt::format("{}::.DELTA_.GRPC must not have a cluster name " @@ -177,7 +178,7 @@ TEST(UtilityTest, FactoryForGrpcApiConfigSource) { api_config_source.add_cluster_names(); api_config_source.add_cluster_names(); EXPECT_THAT(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, - scope, false) + scope, false, 0) .status() .message(), ContainsRegex(fmt::format("{}::.DELTA_.GRPC must not have a cluster name " @@ -191,7 +192,7 @@ TEST(UtilityTest, FactoryForGrpcApiConfigSource) { api_config_source.add_grpc_services()->mutable_envoy_grpc()->set_cluster_name("foo"); // this also logs a warning for configuring gRPC clusters for a REST API config. EXPECT_THAT(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, - scope, false) + scope, false, 0) .status() .message(), ContainsRegex(fmt::format("{}, if not a gRPC type, must not have a gRPC service " @@ -205,7 +206,7 @@ TEST(UtilityTest, FactoryForGrpcApiConfigSource) { api_config_source.add_cluster_names("foo"); EXPECT_THAT( Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, scope, - false) + false, 0) .status() .message(), ContainsRegex(fmt::format("{} type must be gRPC:", api_config_source.GetTypeName()))); @@ -220,7 +221,7 @@ TEST(UtilityTest, FactoryForGrpcApiConfigSource) { EXPECT_CALL(async_client_manager, factoryForGrpcService(ProtoEq(expected_grpc_service), Ref(scope), false)); EXPECT_TRUE(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, - scope, false) + scope, false, 0) .ok()); } @@ -231,9 +232,76 @@ TEST(UtilityTest, FactoryForGrpcApiConfigSource) { EXPECT_CALL( async_client_manager, factoryForGrpcService(ProtoEq(api_config_source.grpc_services(0)), Ref(scope), true)); - EXPECT_TRUE( - Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, scope, true) - .ok()); + EXPECT_TRUE(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, + scope, true, 0) + .ok()); + } +} + +// Validates that when failover is supported, the validation works as expected. +TEST(UtilityTest, FactoryForGrpcApiConfigSourceWithFailover) { + // When envoy.restart_features.xds_failover_support is deprecated, the + // following tests will need to be merged with the tests in + // FactoryForGrpcApiConfigSource. + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.restart_features.xds_failover_support", "true"}}); + + NiceMock async_client_manager; + Stats::MockStore store; + Stats::Scope& scope = *store.rootScope(); + + // No more than 2 config sources. + { + envoy::config::core::v3::ApiConfigSource api_config_source; + api_config_source.set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); + api_config_source.add_grpc_services(); + api_config_source.add_grpc_services(); + api_config_source.add_grpc_services(); + EXPECT_THAT(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, + scope, false, 0) + .status() + .message(), + ContainsRegex(fmt::format( + "{}::.DELTA_.GRPC must have no more than 2 gRPC services specified:", + api_config_source.GetTypeName()))); + } + + // A single gRPC service is valid. + { + envoy::config::core::v3::ApiConfigSource api_config_source; + api_config_source.set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); + api_config_source.add_grpc_services()->mutable_envoy_grpc()->set_cluster_name("foo"); + envoy::config::core::v3::GrpcService expected_grpc_service; + expected_grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); + EXPECT_CALL(async_client_manager, + factoryForGrpcService(ProtoEq(expected_grpc_service), Ref(scope), false)); + EXPECT_TRUE(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, + scope, false, 0) + .ok()); + } + + // 2 gRPC services is valid. + { + envoy::config::core::v3::ApiConfigSource api_config_source; + api_config_source.set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); + api_config_source.add_grpc_services()->mutable_envoy_grpc()->set_cluster_name("foo"); + api_config_source.add_grpc_services()->mutable_envoy_grpc()->set_cluster_name("bar"); + + envoy::config::core::v3::GrpcService expected_grpc_service_foo; + expected_grpc_service_foo.mutable_envoy_grpc()->set_cluster_name("foo"); + EXPECT_CALL(async_client_manager, + factoryForGrpcService(ProtoEq(expected_grpc_service_foo), Ref(scope), false)); + EXPECT_TRUE(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, + scope, false, 0) + .ok()); + + envoy::config::core::v3::GrpcService expected_grpc_service_bar; + expected_grpc_service_bar.mutable_envoy_grpc()->set_cluster_name("bar"); + EXPECT_CALL(async_client_manager, + factoryForGrpcService(ProtoEq(expected_grpc_service_bar), Ref(scope), false)); + EXPECT_TRUE(Utility::factoryForGrpcApiConfigSource(async_client_manager, api_config_source, + scope, false, 1) + .ok()); } } diff --git a/test/extensions/config_subscription/common/subscription_factory_impl_test.cc b/test/extensions/config_subscription/common/subscription_factory_impl_test.cc index ed4470656532..34ac0dfb02eb 100644 --- a/test/extensions/config_subscription/common/subscription_factory_impl_test.cc +++ b/test/extensions/config_subscription/common/subscription_factory_impl_test.cc @@ -213,11 +213,92 @@ TEST_P(SubscriptionFactoryTestUnifiedOrLegacyMux, GrpcClusterMultiton) { EXPECT_CALL(cm_, primaryClusters()).WillRepeatedly(ReturnRef(primary_clusters)); EXPECT_THROW_WITH_REGEX(subscriptionFromConfigSource(config), EnvoyException, - fmt::format("{}::.DELTA_.GRPC must have a " - "single gRPC service specified:", + fmt::format("{}::.DELTA_.GRPC must have no " + "more than 1 gRPC services specified:", config.mutable_api_config_source()->GetTypeName())); } +// Validate that failover is accepted. +TEST_P(SubscriptionFactoryTestUnifiedOrLegacyMux, GrpcClusterMultitonFailover) { + // Once "envoy.restart_features.xds_failover_support" is deprecated, the + // "GrpcClusterMultiton" test above will be removed and this one kept. + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.restart_features.xds_failover_support", "true"}}); + envoy::config::core::v3::ConfigSource config; + Upstream::ClusterManager::ClusterSet primary_clusters; + + config.mutable_api_config_source()->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); + config.mutable_api_config_source()->set_transport_api_version(envoy::config::core::v3::V3); + config.mutable_api_config_source()->mutable_refresh_delay()->set_seconds(1); + + // A single entry is accepted. + config.mutable_api_config_source()->add_grpc_services()->mutable_envoy_grpc()->set_cluster_name( + "static_cluster_foo"); + primary_clusters.insert("static_cluster_foo"); + + { + envoy::config::core::v3::GrpcService expected_grpc_service; + expected_grpc_service.mutable_envoy_grpc()->set_cluster_name("static_cluster_foo"); + + EXPECT_CALL(cm_, primaryClusters()).WillOnce(ReturnRef(primary_clusters)); + EXPECT_CALL(cm_, grpcAsyncClientManager()).WillOnce(ReturnRef(cm_.async_client_manager_)); + EXPECT_CALL(cm_.async_client_manager_, + factoryForGrpcService(ProtoEq(expected_grpc_service), _, _)) + .WillOnce(Invoke([](const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) { + auto async_client_factory = std::make_unique(); + EXPECT_CALL(*async_client_factory, createUncachedRawAsyncClient()).WillOnce(Invoke([] { + return std::make_unique(); + })); + return async_client_factory; + })); + EXPECT_CALL(dispatcher_, createTimer_(_)); + + subscriptionFromConfigSource(config); + } + + // 2 entries (primary and failover) are accepted. + config.mutable_api_config_source()->add_grpc_services()->mutable_envoy_grpc()->set_cluster_name( + "static_cluster_bar"); + primary_clusters.insert("static_cluster_bar"); + + { + envoy::config::core::v3::GrpcService expected_grpc_service; + expected_grpc_service.mutable_envoy_grpc()->set_cluster_name("static_cluster_foo"); + + EXPECT_CALL(cm_, primaryClusters()).WillOnce(ReturnRef(primary_clusters)); + EXPECT_CALL(cm_, grpcAsyncClientManager()).WillOnce(ReturnRef(cm_.async_client_manager_)); + // A factory will only be created for the primary source, because the + // failover source is only supported for ADS. + EXPECT_CALL(cm_.async_client_manager_, + factoryForGrpcService(ProtoEq(expected_grpc_service), _, _)) + .WillOnce(Invoke([](const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) { + auto async_client_factory = std::make_unique(); + EXPECT_CALL(*async_client_factory, createUncachedRawAsyncClient()).WillOnce(Invoke([] { + return std::make_unique(); + })); + return async_client_factory; + })); + EXPECT_CALL(dispatcher_, createTimer_(_)); + + subscriptionFromConfigSource(config); + } + + // 3 entries are rejected. + config.mutable_api_config_source()->add_grpc_services()->mutable_envoy_grpc()->set_cluster_name( + "static_cluster_baz"); + primary_clusters.insert("static_cluster_baz"); + + { + EXPECT_CALL(cm_, primaryClusters()).Times(2).WillRepeatedly(ReturnRef(primary_clusters)); + EXPECT_CALL(cm_, grpcAsyncClientManager()).WillRepeatedly(ReturnRef(cm_.async_client_manager_)); + + EXPECT_THROW_WITH_REGEX(subscriptionFromConfigSource(config), EnvoyException, + fmt::format("{}::.DELTA_.GRPC must have no " + "more than 2 gRPC services specified:", + config.mutable_api_config_source()->GetTypeName())); + } +} + TEST_F(SubscriptionFactoryTest, FilesystemSubscription) { envoy::config::core::v3::ConfigSource config; std::string test_path = TestEnvironment::temporaryDirectory(); diff --git a/test/extensions/config_subscription/grpc/BUILD b/test/extensions/config_subscription/grpc/BUILD index a5fd0ef8df0a..b3f06ff1c743 100644 --- a/test/extensions/config_subscription/grpc/BUILD +++ b/test/extensions/config_subscription/grpc/BUILD @@ -261,3 +261,21 @@ envoy_cc_test( "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], ) + +envoy_cc_test( + name = "xds_failover_integration_test", + srcs = + ["xds_failover_integration_test.cc"], + deps = [ + "//source/common/config:protobuf_link_hacks", + "//source/common/protobuf:utility_lib", + "//test/common/grpc:grpc_client_integration_lib", + "//test/integration:ads_integration_lib", + "//test/integration:http_integration_lib", + "//test/test_common:network_utility_lib", + "//test/test_common:resources_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc b/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc new file mode 100644 index 000000000000..7b30041ddcb7 --- /dev/null +++ b/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc @@ -0,0 +1,575 @@ +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" + +#include "source/common/common/logger.h" +#include "source/common/tls/context_config_impl.h" +#include "source/common/tls/server_ssl_socket.h" +#include "source/common/tls/ssl_socket.h" + +#include "test/integration/ads_integration.h" +#include "test/integration/fake_upstream.h" +#include "test/integration/http_integration.h" +#include "test/test_common/environment.h" + +#include "gtest/gtest.h" + +namespace Envoy { + +namespace { +const auto CdsTypeUrl = Config::getTypeUrl(); +const auto EdsTypeUrl = Config::getTypeUrl(); +const auto LdsTypeUrl = Config::getTypeUrl(); +} // namespace + +// Tests the use of Envoy with a primary and failover sources. +class XdsFailoverAdsIntegrationTest : public AdsDeltaSotwIntegrationSubStateParamTest, + public HttpIntegrationTest { +public: + XdsFailoverAdsIntegrationTest() + : HttpIntegrationTest( + Http::CodecType::HTTP2, ipVersion(), + ConfigHelper::adsBootstrap((sotwOrDelta() == Grpc::SotwOrDelta::Sotw) || + (sotwOrDelta() == Grpc::SotwOrDelta::UnifiedSotw) + ? "GRPC" + : "DELTA_GRPC")) { + config_helper_.addRuntimeOverride("envoy.restart_features.xds_failover_support", "true"); + config_helper_.addRuntimeOverride("envoy.reloadable_features.unified_mux", + (sotwOrDelta() == Grpc::SotwOrDelta::UnifiedSotw || + sotwOrDelta() == Grpc::SotwOrDelta::UnifiedDelta) + ? "true" + : "false"); + use_lds_ = false; + create_xds_upstream_ = true; + tls_xds_upstream_ = true; + sotw_or_delta_ = sotwOrDelta(); + setUpstreamProtocol(Http::CodecType::HTTP2); + } + + void TearDown() override { + cleanUpConnection(failover_xds_connection_); + cleanUpXdsConnection(); + } + + void initialize() override { initialize(true); } + + void initialize(bool failover_defined) { + failover_defined_ = failover_defined; + + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // Configure the primary ADS gRPC. + auto* ads_config = bootstrap.mutable_dynamic_resources()->mutable_ads_config(); + auto* grpc_service = ads_config->add_grpc_services(); + setGrpcService(*grpc_service, "ads_cluster", xds_upstream_->localAddress()); + auto* ads_cluster = bootstrap.mutable_static_resources()->add_clusters(); + ads_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + ads_cluster->set_name("ads_cluster"); + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext context; + auto* validation_context = context.mutable_common_tls_context()->mutable_validation_context(); + validation_context->mutable_trusted_ca()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem")); + auto* san_matcher = validation_context->add_match_typed_subject_alt_names(); + san_matcher->mutable_matcher()->set_suffix("lyft.com"); + san_matcher->set_san_type( + envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS); + if (clientType() == Grpc::ClientType::GoogleGrpc) { + auto* google_grpc = grpc_service->mutable_google_grpc(); + auto* ssl_creds = google_grpc->mutable_channel_credentials()->mutable_ssl_credentials(); + ssl_creds->mutable_root_certs()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem")); + } + ads_cluster->mutable_transport_socket()->set_name("envoy.transport_sockets.tls"); + ads_cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom(context); + if (failover_defined_) { + // Configure the API to use a failover gRPC service. + auto* failover_grpc_service = ads_config->add_grpc_services(); + setGrpcService(*failover_grpc_service, "failover_ads_cluster", + failover_xds_upstream_->localAddress()); + auto* failover_ads_cluster = bootstrap.mutable_static_resources()->add_clusters(); + failover_ads_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + failover_ads_cluster->set_name("failover_ads_cluster"); + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext failover_context; + auto* failover_validation_context = + failover_context.mutable_common_tls_context()->mutable_validation_context(); + failover_validation_context->mutable_trusted_ca()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem")); + auto* failover_san_matcher = + failover_validation_context->add_match_typed_subject_alt_names(); + failover_san_matcher->mutable_matcher()->set_suffix("lyft.com"); + failover_san_matcher->set_san_type( + envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS); + if (clientType() == Grpc::ClientType::GoogleGrpc) { + auto* failover_google_grpc = failover_grpc_service->mutable_google_grpc(); + auto* failover_ssl_creds = + failover_google_grpc->mutable_channel_credentials()->mutable_ssl_credentials(); + failover_ssl_creds->mutable_root_certs()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem")); + } + failover_ads_cluster->mutable_transport_socket()->set_name("envoy.transport_sockets.tls"); + failover_ads_cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom( + failover_context); + } + }); + HttpIntegrationTest::initialize(); + // Do not respond to the initial primary stream request. + } + + void createFailoverXdsUpstream() { + if (tls_xds_upstream_ == false) { + addFakeUpstream(Http::CodecType::HTTP2); + } else { + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context; + auto* common_tls_context = tls_context.mutable_common_tls_context(); + common_tls_context->add_alpn_protocols(Http::Utility::AlpnNames::get().Http2); + auto* tls_cert = common_tls_context->add_tls_certificates(); + tls_cert->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcert.pem")); + tls_cert->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/upstreamkey.pem")); + auto cfg = *Extensions::TransportSockets::Tls::ServerContextConfigImpl::create( + tls_context, factory_context_); + // upstream_stats_store_ should have been initialized be prior call to + // BaseIntegrationTest::createXdsUpstream(). + ASSERT(upstream_stats_store_ != nullptr); + auto context = *Extensions::TransportSockets::Tls::ServerSslSocketFactory::create( + std::move(cfg), context_manager_, *upstream_stats_store_->rootScope(), + std::vector{}); + addFakeUpstream(std::move(context), Http::CodecType::HTTP2, /*autonomous_upstream=*/false); + } + failover_xds_upstream_ = fake_upstreams_.back().get(); + } + + void initializeFailoverXdsStream() { + if (failover_xds_stream_ == nullptr) { + auto result = failover_xds_connection_->waitForNewStream(*dispatcher_, failover_xds_stream_); + RELEASE_ASSERT(result, result.message()); + failover_xds_stream_->startGrpcStream(); + } + } + + void createXdsUpstream() override { + BaseIntegrationTest::createXdsUpstream(); + // Setup the failover xDS upstream. + createFailoverXdsUpstream(); + } + + void cleanUpConnection(FakeHttpConnectionPtr& connection) { + if (connection != nullptr) { + AssertionResult result = connection->close(); + RELEASE_ASSERT(result, result.message()); + result = connection->waitForDisconnect(); + RELEASE_ASSERT(result, result.message()); + connection.reset(); + } + } + + // Waits for a primary source connected and immediately disconnects. + void primaryConnectionFailure() { + AssertionResult result = xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_); + RELEASE_ASSERT(result, result.message()); + result = xds_connection_->close(); + RELEASE_ASSERT(result, result.message()); + } + + envoy::config::endpoint::v3::ClusterLoadAssignment + buildClusterLoadAssignment(const std::string& name) { + return ConfigHelper::buildClusterLoadAssignment( + name, Network::Test::getLoopbackAddressString(ipVersion()), + fake_upstreams_[0]->localAddress()->ip()->port()); + } + + void makeSingleRequest() { + registerTestServerPorts({"http"}); + testRouterHeaderOnlyRequestAndResponse(); + cleanupUpstreamAndDownstream(); + } + + envoy::config::listener::v3::Listener buildSimpleListener(const std::string& listener_name, + const std::string& cluster_name) { + std::string hcm = fmt::format( + R"EOF( + filters: + - name: http + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: {} + codec_type: HTTP2 + route_config: + name: route_config_1 + virtual_hosts: + - name: integration + domains: ["*"] + routes: + - match: {{ prefix: "/" }} + route: {{ cluster: "{}" }} + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + )EOF", + "ads_test", cluster_name); + return ConfigHelper::buildBaseListener( + listener_name, Network::Test::getLoopbackAddressString(ipVersion()), hcm); + } + + void validateAllXdsResponsesAndDataplaneRequest(FakeStream* xds_stream) { + EXPECT_TRUE(compareDiscoveryRequest(CdsTypeUrl, "", {}, {}, {}, true, + Grpc::Status::WellKnownGrpcStatus::Ok, "", xds_stream)); + sendDiscoveryResponse( + CdsTypeUrl, {ConfigHelper::buildCluster("cluster_0")}, + {ConfigHelper::buildCluster("cluster_0")}, {}, "1", {}, xds_stream); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); + EXPECT_TRUE(compareDiscoveryRequest(EdsTypeUrl, "", {"cluster_0"}, {"cluster_0"}, {}, false, + Grpc::Status::WellKnownGrpcStatus::Ok, "", xds_stream)); + sendDiscoveryResponse( + EdsTypeUrl, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1", {}, xds_stream); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 0); + + EXPECT_TRUE(compareDiscoveryRequest(CdsTypeUrl, "1", {}, {}, {}, false, + Grpc::Status::WellKnownGrpcStatus::Ok, "", xds_stream)); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "", {}, {}, {}, false, + Grpc::Status::WellKnownGrpcStatus::Ok, "", xds_stream)); + + sendDiscoveryResponse( + LdsTypeUrl, {buildSimpleListener("listener_0", "cluster_0")}, + {buildSimpleListener("listener_0", "cluster_0")}, {}, "1", {}, xds_stream); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"cluster_0"}, {}, {}, false, + Grpc::Status::WellKnownGrpcStatus::Ok, "", xds_stream)); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "1", {}, {}, {}, false, + Grpc::Status::WellKnownGrpcStatus::Ok, "", xds_stream)); + + test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + makeSingleRequest(); + } + + bool failover_defined_; + // Holds the failover xDS server data (if needed). + FakeUpstream* failover_xds_upstream_; + FakeHttpConnectionPtr failover_xds_connection_; + FakeStreamPtr failover_xds_stream_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDeltaWildcard, XdsFailoverAdsIntegrationTest, + DELTA_SOTW_UNIFIED_GRPC_CLIENT_INTEGRATION_PARAMS); + +// Validate that when there's no failover defined (but runtime flag enabled), +// the primary is used. +TEST_P(XdsFailoverAdsIntegrationTest, NoFailoverBasic) { + initialize(false); + + createXdsConnection(); + AssertionResult result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + xds_stream_->startGrpcStream(); + + // Ensure basic flow using the primary (when failover is not defined) works. + validateAllXdsResponsesAndDataplaneRequest(xds_stream_.get()); +} + +// Validate that when there's failover defined and the primary is available, +// then a connection to the failover won't be attempted. +TEST_P(XdsFailoverAdsIntegrationTest, FailoverNotAttemptedWhenPrimaryAvailable) { + // This test does not work when GoogleGrpc is used, because GoogleGrpc + // attempts to create the connection when the client is created (not when it + // actually attempts to send a request). This is due to the call to GetChannel: + // https://github.com/envoyproxy/envoy/blob/c9848c7be992c489a16a3218ec4bf373c9616d70/source/common/grpc/google_async_client_impl.cc#L106 + SKIP_IF_GRPC_CLIENT(Grpc::ClientType::GoogleGrpc); + initialize(); + + createXdsConnection(); + AssertionResult result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + xds_stream_->startGrpcStream(); + + // Ensure basic flow using the primary (while failover is defined) works. + validateAllXdsResponsesAndDataplaneRequest(xds_stream_.get()); + + // A failover connection should not be attempted, so a failure is expected here. + // Setting smaller timeout to avoid long execution times. + EXPECT_FALSE(failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_, + std::chrono::seconds(5))); +} + +// Validates that when there's a failover defined, and the primary isn't responding, +// then Envoy will use the failover, and will receive a valid config. +TEST_P(XdsFailoverAdsIntegrationTest, StartupPrimaryNotResponding) { + initialize(); + + // Expect a connection to the primary. Reject the connection immediately. + primaryConnectionFailure(); + + // Expect another connection attempt to the primary. Reject the stream (gRPC failure) immediately. + // As this is a 2nd consecutive failure, it will trigger failover. + primaryConnectionFailure(); + + AssertionResult result = + failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); + RELEASE_ASSERT(result, result.message()); + // Failover is healthy, start the ADS gRPC stream. + result = failover_xds_connection_->waitForNewStream(*dispatcher_, failover_xds_stream_); + RELEASE_ASSERT(result, result.message()); + failover_xds_stream_->startGrpcStream(); + + // Ensure basic flow using the failover source works. + validateAllXdsResponsesAndDataplaneRequest(failover_xds_stream_.get()); +} + +// Validates that when there's a failover defined, and the primary returns a +// gRPC failure, then Envoy will use the failover, and will receive a valid config. +TEST_P(XdsFailoverAdsIntegrationTest, StartupPrimaryGrpcFailure) { +#ifdef ENVOY_ENABLE_UHV + // With UHV the finishGrpcStream() isn't detected as invalid frame because of + // no ":status" header, unless "envoy.reloadable_features.enable_universal_header_validator" + // is also enabled. + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif + initialize(); + + // Expect a connection to the primary. Send a gRPC failure immediately. + AssertionResult result = xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_); + RELEASE_ASSERT(result, result.message()); + result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + xds_stream_->finishGrpcStream(Grpc::Status::Internal); + + // Second attempt to the primary. + // When using GoogleGrpc the same connection is reused, whereas for EnvoyGrpc + // a new connection will be established. + if (clientType() == Grpc::ClientType::EnvoyGrpc) { + result = xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_); + RELEASE_ASSERT(result, result.message()); + } + result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + xds_stream_->finishGrpcStream(Grpc::Status::Internal); + + ASSERT(failover_xds_connection_ == nullptr); + result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); + RELEASE_ASSERT(result, result.message()); + // Failover is healthy, start the ADS gRPC stream. + result = failover_xds_connection_->waitForNewStream(*dispatcher_, failover_xds_stream_); + RELEASE_ASSERT(result, result.message()); + failover_xds_stream_->startGrpcStream(); + + // Ensure basic flow using the failover source works. + validateAllXdsResponsesAndDataplaneRequest(failover_xds_stream_.get()); +} + +// Validates that when there's a failover defined, and the primary returns a +// gRPC failure after sending headers, then Envoy will use the failover, and will receive a valid +// config. +TEST_P(XdsFailoverAdsIntegrationTest, StartupPrimaryGrpcFailureAfterHeaders) { +#ifdef ENVOY_ENABLE_UHV + // With UHV the finishGrpcStream() isn't detected as invalid frame because of + // no ":status" header, unless "envoy.reloadable_features.enable_universal_header_validator" + // is also enabled. + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif + initialize(); + + // Expect a connection to the primary. Send a gRPC failure immediately. + AssertionResult result = xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_); + RELEASE_ASSERT(result, result.message()); + result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + xds_stream_->startGrpcStream(); + xds_stream_->finishGrpcStream(Grpc::Status::Internal); + + // Second attempt to the primary, reusing stream as headers were previously + // sent. + result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + xds_stream_->startGrpcStream(); + xds_stream_->finishGrpcStream(Grpc::Status::Internal); + + ASSERT(failover_xds_connection_ == nullptr); + result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); + RELEASE_ASSERT(result, result.message()); + // Failover is healthy, start the ADS gRPC stream. + result = failover_xds_connection_->waitForNewStream(*dispatcher_, failover_xds_stream_); + RELEASE_ASSERT(result, result.message()); + failover_xds_stream_->startGrpcStream(); + + // Ensure basic flow using the failover source works. + validateAllXdsResponsesAndDataplaneRequest(failover_xds_stream_.get()); +} + +// Validate that once primary answers, failover will not be used, even after disconnecting. +TEST_P(XdsFailoverAdsIntegrationTest, NoFailoverUseAfterPrimaryResponse) { +#ifdef ENVOY_ENABLE_UHV + // With UHV the finishGrpcStream() isn't detected as invalid frame because of + // no ":status" header, unless "envoy.reloadable_features.enable_universal_header_validator" + // is also enabled. + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif + initialize(); + + // Let the primary source respond. + createXdsConnection(); + AssertionResult result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + xds_stream_->startGrpcStream(); + + // Ensure basic flow with failover works. + EXPECT_TRUE(compareDiscoveryRequest(CdsTypeUrl, "", {}, {}, {}, true, + Grpc::Status::WellKnownGrpcStatus::Ok, "", + xds_stream_.get())); + sendDiscoveryResponse( + CdsTypeUrl, {ConfigHelper::buildCluster("cluster_0")}, + {ConfigHelper::buildCluster("cluster_0")}, {}, "1", {}, xds_stream_.get()); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); + + // Envoy has received a CDS response, it means the primary is available. + // Now disconnect the primary. + xds_stream_->finishGrpcStream(Grpc::Status::Internal); + + // In this case (received a response), both EnvoyGrpc and GoogleGrpc keep the connection open. + result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + // Immediately fail the connection. + xds_stream_->finishGrpcStream(Grpc::Status::Internal); + + // Ensure that Envoy still attempts to connect to the primary, + // and keep disconnecting a few times and validate that the failover + // connection isn't attempted. + for (int i = 3; i < 5; ++i) { + // EnvoyGrpc will disconnect if the gRPC stream is immediately closed (as + // done above). + if (clientType() == Grpc::ClientType::EnvoyGrpc) { + result = xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_); + RELEASE_ASSERT(result, result.message()); + } + result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + // Immediately fail the connection. + xds_stream_->finishGrpcStream(Grpc::Status::Internal); + } + + // When GoogleGrpc is used, a connection to the failover_xds_upstream will be + // attempted, but no stream will be created. When EnvoyGrpc is used, no + // connection to the failover will be attempted. + if (clientType() == Grpc::ClientType::EnvoyGrpc) { + // A failover connection should not be attempted, so a failure is expected here. + // Setting smaller timeout to avoid long execution times. + EXPECT_FALSE(failover_xds_upstream_->waitForHttpConnection( + *dispatcher_, failover_xds_connection_, std::chrono::seconds(1))); + } else { + result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); + RELEASE_ASSERT(result, result.message()); + EXPECT_FALSE(failover_xds_connection_->waitForNewStream(*dispatcher_, failover_xds_stream_, + std::chrono::seconds(1))); + } + + // Allow a connection to the primary. + // Expect a connection to the primary when using EnvoyGrpc. + // In case GoogleGrpc is used the current connection will be reused (new stream). + if (clientType() == Grpc::ClientType::EnvoyGrpc) { + result = xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_); + RELEASE_ASSERT(result, result.message()); + } + result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + xds_stream_->startGrpcStream(); + + // The rest will be a normal primary source xDS back and forth. + validateAllXdsResponsesAndDataplaneRequest(xds_stream_.get()); +} + +// Validate that once failover answers, primary will not be used, even after disconnecting. +TEST_P(XdsFailoverAdsIntegrationTest, NoPrimaryUseAfterFailoverResponse) { +#ifdef ENVOY_ENABLE_UHV + // With UHV the finishGrpcStream() isn't detected as invalid frame because of + // no ":status" header, unless "envoy.reloadable_features.enable_universal_header_validator" + // is also enabled. + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif + initialize(); + + // 2 consecutive primary failures. + // Expect a connection to the primary. Reject the connection immediately. + primaryConnectionFailure(); + // Expect another connection attempt to the primary. Reject the stream (gRPC failure) immediately. + // As this is a 2nd consecutive failure, it will trigger failover. + primaryConnectionFailure(); + + AssertionResult result = + failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); + RELEASE_ASSERT(result, result.message()); + // Failover is healthy, start the ADS gRPC stream. + result = failover_xds_connection_->waitForNewStream(*dispatcher_, failover_xds_stream_); + RELEASE_ASSERT(result, result.message()); + failover_xds_stream_->startGrpcStream(); + + // Ensure basic flow with failover works. + EXPECT_TRUE(compareDiscoveryRequest(CdsTypeUrl, "", {}, {}, {}, true, + Grpc::Status::WellKnownGrpcStatus::Ok, "", + failover_xds_stream_.get())); + sendDiscoveryResponse( + CdsTypeUrl, {ConfigHelper::buildCluster("cluster_0")}, + {ConfigHelper::buildCluster("cluster_0")}, {}, "1", {}, failover_xds_stream_.get()); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); + + // Envoy has received a CDS response, it means the primary is available. + // Now disconnect the primary. + failover_xds_stream_->finishGrpcStream(Grpc::Status::Internal); + + // In this case (received a response), both EnvoyGrpc and GoogleGrpc keep the connection open. + result = failover_xds_connection_->waitForNewStream(*dispatcher_, failover_xds_stream_); + RELEASE_ASSERT(result, result.message()); + // Immediately fail the connection. + failover_xds_stream_->finishGrpcStream(Grpc::Status::Internal); + + // Ensure that Envoy still attempts to connect to the primary, + // and keep disconnecting a few times and validate that the failover + // connection isn't attempted. + for (int i = 3; i < 5; ++i) { + // EnvoyGrpc will disconnect if the gRPC stream is immediately closed (as + // done above). + if (clientType() == Grpc::ClientType::EnvoyGrpc) { + result = + failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); + RELEASE_ASSERT(result, result.message()); + } + result = failover_xds_connection_->waitForNewStream(*dispatcher_, failover_xds_stream_); + RELEASE_ASSERT(result, result.message()); + // Immediately fail the connection. + failover_xds_stream_->finishGrpcStream(Grpc::Status::Internal); + } + + // When GoogleGrpc is used, a connection to the (primary) xds_upstream will be + // attempted, but no stream will be created. When EnvoyGrpc is used, no + // connection to the primary will be attempted. + if (clientType() == Grpc::ClientType::EnvoyGrpc) { + // A primary connection should not be attempted, so a failure is expected here. + // Setting smaller timeout to avoid long execution times. + EXPECT_FALSE(xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_, + std::chrono::seconds(1))); + } else { + result = xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_); + RELEASE_ASSERT(result, result.message()); + EXPECT_FALSE( + xds_connection_->waitForNewStream(*dispatcher_, xds_stream_, std::chrono::seconds(1))); + } + + // Allow a connection to the failover. + // Expect a connection to the failover when using EnvoyGrpc. + // In case GoogleGrpc is used the current connection will be reused (new stream). + if (clientType() == Grpc::ClientType::EnvoyGrpc) { + result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); + RELEASE_ASSERT(result, result.message()); + } + result = failover_xds_connection_->waitForNewStream(*dispatcher_, failover_xds_stream_); + failover_xds_stream_->startGrpcStream(); + + // The rest will be a normal failover source xDS back and forth. + validateAllXdsResponsesAndDataplaneRequest(failover_xds_stream_.get()); +} +} // namespace Envoy diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index b6cc8fb7336d..6278768d6d2d 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -167,7 +167,7 @@ class BaseIntegrationTest : protected Logger::Loggable { envoy::config::core::v3::Node last_node_; // Functions for testing reloadable config (xDS) - void createXdsUpstream(); + virtual void createXdsUpstream(); void createXdsConnection(); void cleanUpXdsConnection(); From 416e7a64d1d3f72bf3309938e22ca5f006fe429f Mon Sep 17 00:00:00 2001 From: code Date: Sat, 29 Jun 2024 10:46:43 +0800 Subject: [PATCH 17/31] fix a bug of local rate limit about the x-ratelimited-* headers setting (#34917) Signed-off-by: wbpcode Co-authored-by: wbpcode --- changelogs/current.yaml | 5 + .../http/local_ratelimit/local_ratelimit.cc | 92 +++++++++---------- .../http/local_ratelimit/local_ratelimit.h | 6 +- .../http/local_ratelimit/filter_test.cc | 16 ++++ 4 files changed, 67 insertions(+), 52 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 348a1eb46d7a..d4fa242f22c0 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -214,6 +214,11 @@ bug_fixes: Validate http service path_prefix :ref:`path_prefix `, Validate http service path_prefix configuration must start with ``/``. +- area: local_ratelimit + change: | + Fixed a bug where the local rate limit filter would crash when the + :ref:`enable_x_ratelimit_headers ` + is set to ``DRAFT_VERSION_03`` and a send local reply is triggered before the rate limit filter is executed. - area: admin change: | Fixed missing :ref:`additional addresses ` diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc index e92274029cec..ed759c2ea48d 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc @@ -133,16 +133,25 @@ bool FilterConfig::enforced() const { } Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool) { - const auto* config = getConfig(); + const auto* route_config = + Http::Utility::resolveMostSpecificPerFilterConfig(decoder_callbacks_); + + // We can never assume that the configuration/route will not change between + // decodeHeaders() and encodeHeaders(). + // Store the configuration because we will use it later in encodeHeaders(). + if (route_config != nullptr) { + ASSERT(used_config_ == config_.get()); + used_config_ = route_config; // Overwrite the used configuration. + } - if (!config->enabled()) { + if (!used_config_->enabled()) { return Http::FilterHeadersStatus::Continue; } - config->stats().enabled_.inc(); + used_config_->stats().enabled_.inc(); std::vector descriptors; - if (config->hasDescriptors()) { + if (used_config_->hasDescriptors()) { populateDescriptors(descriptors, headers); } @@ -158,35 +167,35 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, } if (requestAllowed(descriptors)) { - config->stats().ok_.inc(); + used_config_->stats().ok_.inc(); return Http::FilterHeadersStatus::Continue; } - config->stats().rate_limited_.inc(); + used_config_->stats().rate_limited_.inc(); - if (!config->enforced()) { - config->requestHeadersParser().evaluateHeaders(headers, decoder_callbacks_->streamInfo()); + if (!used_config_->enforced()) { + used_config_->requestHeadersParser().evaluateHeaders(headers, decoder_callbacks_->streamInfo()); return Http::FilterHeadersStatus::Continue; } - config->stats().enforced_.inc(); + used_config_->stats().enforced_.inc(); decoder_callbacks_->sendLocalReply( - config->status(), "local_rate_limited", - [this, config](Http::HeaderMap& headers) { - config->responseHeadersParser().evaluateHeaders(headers, decoder_callbacks_->streamInfo()); + used_config_->status(), "local_rate_limited", + [this](Http::HeaderMap& headers) { + used_config_->responseHeadersParser().evaluateHeaders(headers, + decoder_callbacks_->streamInfo()); }, - config->rateLimitedGrpcStatus(), "local_rate_limited"); + used_config_->rateLimitedGrpcStatus(), "local_rate_limited"); decoder_callbacks_->streamInfo().setResponseFlag(StreamInfo::CoreResponseFlag::RateLimited); return Http::FilterHeadersStatus::StopIteration; } Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) { - const auto* config = getConfig(); - - if (config->enabled() && config->enableXRateLimitHeaders()) { - ASSERT(stored_descriptors_.has_value()); + // We can never assume the decodeHeaders() was called before encodeHeaders(). + if (used_config_->enabled() && used_config_->enableXRateLimitHeaders() && + stored_descriptors_.has_value()) { auto limit = maxTokens(stored_descriptors_.value()); auto remaining = remainingTokens(stored_descriptors_.value()); auto reset = remainingFillInterval(stored_descriptors_.value()); @@ -203,37 +212,32 @@ Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers } bool Filter::requestAllowed(absl::Span request_descriptors) { - const auto* config = getConfig(); - return config->rateLimitPerConnection() + return used_config_->rateLimitPerConnection() ? getPerConnectionRateLimiter().requestAllowed(request_descriptors) - : config->requestAllowed(request_descriptors); + : used_config_->requestAllowed(request_descriptors); } uint32_t Filter::maxTokens(absl::Span request_descriptors) { - const auto* config = getConfig(); - return config->rateLimitPerConnection() + return used_config_->rateLimitPerConnection() ? getPerConnectionRateLimiter().maxTokens(request_descriptors) - : config->maxTokens(request_descriptors); + : used_config_->maxTokens(request_descriptors); } uint32_t Filter::remainingTokens(absl::Span request_descriptors) { - const auto* config = getConfig(); - return config->rateLimitPerConnection() + return used_config_->rateLimitPerConnection() ? getPerConnectionRateLimiter().remainingTokens(request_descriptors) - : config->remainingTokens(request_descriptors); + : used_config_->remainingTokens(request_descriptors); } int64_t Filter::remainingFillInterval(absl::Span request_descriptors) { - const auto* config = getConfig(); - return config->rateLimitPerConnection() + return used_config_->rateLimitPerConnection() ? getPerConnectionRateLimiter().remainingFillInterval(request_descriptors) - : config->remainingFillInterval(request_descriptors); + : used_config_->remainingFillInterval(request_descriptors); } const Filters::Common::LocalRateLimit::LocalRateLimiterImpl& Filter::getPerConnectionRateLimiter() { - const auto* config = getConfig(); - ASSERT(config->rateLimitPerConnection()); + ASSERT(used_config_->rateLimitPerConnection()); auto typed_state = decoder_callbacks_->streamInfo().filterState()->getDataReadOnly( @@ -241,9 +245,9 @@ const Filters::Common::LocalRateLimit::LocalRateLimiterImpl& Filter::getPerConne if (typed_state == nullptr) { auto limiter = std::make_shared( - config->fillInterval(), config->maxTokens(), config->tokensPerFill(), - decoder_callbacks_->dispatcher(), config->descriptors(), - config->consumeDefaultTokenBucket()); + used_config_->fillInterval(), used_config_->maxTokens(), used_config_->tokensPerFill(), + decoder_callbacks_->dispatcher(), used_config_->descriptors(), + used_config_->consumeDefaultTokenBucket()); decoder_callbacks_->streamInfo().filterState()->setData( PerConnectionRateLimiter::key(), limiter, StreamInfo::FilterState::StateType::ReadOnly, @@ -284,35 +288,23 @@ void Filter::populateDescriptors(std::vector& descri void Filter::populateDescriptors(const Router::RateLimitPolicy& rate_limit_policy, std::vector& descriptors, Http::RequestHeaderMap& headers) { - const auto* config = getConfig(); for (const Router::RateLimitPolicyEntry& rate_limit : - rate_limit_policy.getApplicableRateLimit(config->stage())) { + rate_limit_policy.getApplicableRateLimit(used_config_->stage())) { const std::string& disable_key = rate_limit.disableKey(); if (!disable_key.empty()) { continue; } - rate_limit.populateLocalDescriptors(descriptors, config->localInfo().clusterName(), headers, - decoder_callbacks_->streamInfo()); + rate_limit.populateLocalDescriptors(descriptors, used_config_->localInfo().clusterName(), + headers, decoder_callbacks_->streamInfo()); } } -const FilterConfig* Filter::getConfig() const { - const auto* config = - Http::Utility::resolveMostSpecificPerFilterConfig(decoder_callbacks_); - if (config) { - return config; - } - - return config_.get(); -} - VhRateLimitOptions Filter::getVirtualHostRateLimitOption(const Router::RouteConstSharedPtr& route) { if (route->routeEntry()->includeVirtualHostRateLimits()) { vh_rate_limits_ = VhRateLimitOptions::Include; } else { - const auto* config = getConfig(); - switch (config->virtualHostRateLimits()) { + switch (used_config_->virtualHostRateLimits()) { PANIC_ON_PROTO_ENUM_SENTINEL_VALUES; case envoy::extensions::common::ratelimit::v3::INCLUDE: vh_rate_limits_ = VhRateLimitOptions::Include; diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h index 6bad1e57636b..7640af4835bd 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h @@ -163,7 +163,7 @@ using FilterConfigSharedPtr = std::shared_ptr; */ class Filter : public Http::PassThroughFilter, Logger::Loggable { public: - Filter(FilterConfigSharedPtr config) : config_(config) {} + Filter(FilterConfigSharedPtr config) : config_(config), used_config_(config_.get()) {} // Http::StreamDecoderFilter Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, @@ -188,8 +188,10 @@ class Filter : public Http::PassThroughFilter, Logger::Loggable request_descriptors); int64_t remainingFillInterval(absl::Span request_descriptors); - const FilterConfig* getConfig() const; FilterConfigSharedPtr config_; + // Actual config used for the current request. Is config_ by default, but can be overridden by + // per-route config. + const FilterConfig* used_config_{}; absl::optional> stored_descriptors_; VhRateLimitOptions vh_rate_limits_; diff --git a/test/extensions/filters/http/local_ratelimit/filter_test.cc b/test/extensions/filters/http/local_ratelimit/filter_test.cc index 6994dc46d034..607eb82ad92f 100644 --- a/test/extensions/filters/http/local_ratelimit/filter_test.cc +++ b/test/extensions/filters/http/local_ratelimit/filter_test.cc @@ -317,6 +317,22 @@ TEST_F(FilterTest, RequestRateLimitedXRateLimitHeaders) { EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); } +TEST_F(FilterTest, RequestRateLimitedXRateLimitHeadersWithoutRunningDecodeHeaders) { + setup(fmt::format(config_yaml, "false", "1", "false", "DRAFT_VERSION_03")); + + auto response_headers = Http::TestResponseHeaderMapImpl(); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); + EXPECT_EQ("", response_headers.get_("x-ratelimit-limit")); + EXPECT_EQ("", response_headers.get_("x-ratelimit-remaining")); + EXPECT_EQ("", response_headers.get_("x-ratelimit-reset")); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_2_->encodeHeaders(response_headers, false)); + EXPECT_EQ("", response_headers.get_("x-ratelimit-limit")); + EXPECT_EQ("", response_headers.get_("x-ratelimit-remaining")); + EXPECT_EQ("", response_headers.get_("x-ratelimit-reset")); +} + static constexpr absl::string_view descriptor_config_yaml = R"( stat_prefix: test token_bucket: From 7a9e91073f5fd3f22541fee65f939c47a15bc460 Mon Sep 17 00:00:00 2001 From: RenjieTang Date: Mon, 1 Jul 2024 06:11:40 -0700 Subject: [PATCH 18/31] [QUIC]add QUIC connection close detail to transport close details (#34977) add connection close detail Signed-off-by: Renjie Tang --- .../common/quic/envoy_quic_client_stream.cc | 11 +++++----- .../common/quic/envoy_quic_server_stream.cc | 11 +++++----- test/integration/http_integration.cc | 2 +- .../local_reply_integration_test.cc | 22 +++++++++---------- test/integration/protocol_integration_test.cc | 6 ++--- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc index e2ae5cb296e3..12cfb8ffa88c 100644 --- a/source/common/quic/envoy_quic_client_stream.cc +++ b/source/common/quic/envoy_quic_client_stream.cc @@ -373,11 +373,12 @@ void EnvoyQuicClientStream::ResetWithError(quic::QuicResetStreamError error) { void EnvoyQuicClientStream::OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, quic::ConnectionCloseSource source) { if (!end_stream_decoded_) { - runResetCallbacks(source == quic::ConnectionCloseSource::FROM_SELF - ? quicErrorCodeToEnvoyLocalResetReason(frame.quic_error_code, - session()->OneRttKeysAvailable()) - : quicErrorCodeToEnvoyRemoteResetReason(frame.quic_error_code), - quic::QuicErrorCodeToString(frame.quic_error_code)); + runResetCallbacks( + source == quic::ConnectionCloseSource::FROM_SELF + ? quicErrorCodeToEnvoyLocalResetReason(frame.quic_error_code, + session()->OneRttKeysAvailable()) + : quicErrorCodeToEnvoyRemoteResetReason(frame.quic_error_code), + absl::StrCat(quic::QuicErrorCodeToString(frame.quic_error_code), "|", frame.error_details)); } quic::QuicSpdyClientStream::OnConnectionClosed(frame, source); } diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index 31fb93d72896..70cd0a79cada 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -365,11 +365,12 @@ void EnvoyQuicServerStream::OnConnectionClosed(const quic::QuicConnectionCloseFr // Run reset callback before closing the stream so that the watermark change will not trigger // callbacks. if (!local_end_stream_) { - runResetCallbacks(source == quic::ConnectionCloseSource::FROM_SELF - ? quicErrorCodeToEnvoyLocalResetReason(frame.quic_error_code, - session()->OneRttKeysAvailable()) - : quicErrorCodeToEnvoyRemoteResetReason(frame.quic_error_code), - quic::QuicErrorCodeToString(frame.quic_error_code)); + runResetCallbacks( + source == quic::ConnectionCloseSource::FROM_SELF + ? quicErrorCodeToEnvoyLocalResetReason(frame.quic_error_code, + session()->OneRttKeysAvailable()) + : quicErrorCodeToEnvoyRemoteResetReason(frame.quic_error_code), + absl::StrCat(quic::QuicErrorCodeToString(frame.quic_error_code), "|", frame.error_details)); } quic::QuicSpdyServerStreamBase::OnConnectionClosed(frame, source); } diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 23fa0da0edab..485e2ca65d13 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -842,7 +842,7 @@ void HttpIntegrationTest::testRouterUpstreamDisconnectBeforeRequestComplete() { EXPECT_EQ(response->headers().getProxyStatusValue(), "envoy; error=connection_terminated; " "details=\"upstream_reset_before_response_started{connection_termination|QUIC_NO_" - "ERROR}; UC\""); + "ERROR|Closed_by_application}; UC\""); } else { EXPECT_EQ(response->headers().getProxyStatusValue(), "envoy; error=connection_terminated; " diff --git a/test/integration/local_reply_integration_test.cc b/test/integration/local_reply_integration_test.cc index 132f71ecbcbe..79c2631a5f4e 100644 --- a/test/integration/local_reply_integration_test.cc +++ b/test/integration/local_reply_integration_test.cc @@ -53,7 +53,7 @@ TEST_P(LocalReplyIntegrationTest, MapStatusCodeAndFormatToJson) { expected_body = R"({ "level": "TRACE", "user_agent": null, - "response_body": "upstream connect error or disconnect/reset before headers. reset reason: connection termination, transport failure reason: QUIC_NO_ERROR" + "response_body": "upstream connect error or disconnect/reset before headers. reset reason: connection termination, transport failure reason: QUIC_NO_ERROR|Closed by application" })"; } else { expected_body = R"({ @@ -93,7 +93,7 @@ TEST_P(LocalReplyIntegrationTest, MapStatusCodeAndFormatToJson) { EXPECT_TRUE(response->complete()); EXPECT_EQ("application/json-custom", response->headers().ContentType()->value().getStringView()); if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - EXPECT_EQ("191", response->headers().ContentLength()->value().getStringView()); + EXPECT_EQ("213", response->headers().ContentLength()->value().getStringView()); } else { EXPECT_EQ("150", response->headers().ContentLength()->value().getStringView()); } @@ -102,7 +102,7 @@ TEST_P(LocalReplyIntegrationTest, MapStatusCodeAndFormatToJson) { EXPECT_EQ(response->headers().getProxyStatusValue(), "envoy; error=connection_terminated; " "details=\"upstream_reset_before_response_started{connection_termination|QUIC_NO_" - "ERROR}; UC\""); + "ERROR|Closed_by_application}; UC\""); } else { EXPECT_EQ(response->headers().getProxyStatusValue(), "envoy; error=connection_terminated; " @@ -189,7 +189,7 @@ TEST_P(LocalReplyIntegrationTest, MapStatusCodeAndFormatToJson4Grpc) { if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { expected_grpc_message = R"({ "code": 503, - "message":"upstream connect error or disconnect/reset before headers. reset reason: connection termination, transport failure reason: QUIC_NO_ERROR" + "message":"upstream connect error or disconnect/reset before headers. reset reason: connection termination, transport failure reason: QUIC_NO_ERROR|Closed by application" })"; } else { expected_grpc_message = R"({ @@ -252,7 +252,7 @@ TEST_P(LocalReplyIntegrationTest, MapStatusCodeAndFormat2Text4Grpc) { expected_grpc_message = "upstream connect error or disconnect/reset before headers. reset reason:" " connection termination, transport failure reason: " - "QUIC_NO_ERROR:503:path=/package.service/method%0A"; + "QUIC_NO_ERROR|Closed by application:503:path=/package.service/method%0A"; } else { expected_grpc_message = "upstream connect error or disconnect/reset before headers. reset reason:" @@ -416,7 +416,7 @@ TEST_P(LocalReplyIntegrationTest, ShouldNotMatchAnyFilter) { expected_body = R"({ "level": "TRACE", "response_flags": "UC", - "response_body": "upstream connect error or disconnect/reset before headers. reset reason: connection termination, transport failure reason: QUIC_NO_ERROR" + "response_body": "upstream connect error or disconnect/reset before headers. reset reason: connection termination, transport failure reason: QUIC_NO_ERROR|Closed by application" })"; } else { expected_body = R"({ @@ -456,7 +456,7 @@ TEST_P(LocalReplyIntegrationTest, ShouldNotMatchAnyFilter) { EXPECT_TRUE(response->complete()); EXPECT_EQ("application/json", response->headers().ContentType()->value().getStringView()); if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - EXPECT_EQ("195", response->headers().ContentLength()->value().getStringView()); + EXPECT_EQ("217", response->headers().ContentLength()->value().getStringView()); } else { EXPECT_EQ("154", response->headers().ContentLength()->value().getStringView()); } @@ -524,7 +524,7 @@ TEST_P(LocalReplyIntegrationTest, ShouldMapResponseCodeAndMapToDefaultTextRespon EXPECT_TRUE(response->complete()); EXPECT_EQ("text/plain", response->headers().ContentType()->value().getStringView()); if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - EXPECT_EQ("136", response->headers().ContentLength()->value().getStringView()); + EXPECT_EQ("158", response->headers().ContentLength()->value().getStringView()); } else { EXPECT_EQ("95", response->headers().ContentLength()->value().getStringView()); } @@ -532,9 +532,9 @@ TEST_P(LocalReplyIntegrationTest, ShouldMapResponseCodeAndMapToDefaultTextRespon EXPECT_EQ("551", response->headers().Status()->value().getStringView()); if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - EXPECT_EQ(response->body(), - "upstream connect error or disconnect/reset before headers. reset " - "reason: connection termination, transport failure reason: QUIC_NO_ERROR"); + EXPECT_EQ(response->body(), "upstream connect error or disconnect/reset before headers. reset " + "reason: connection termination, transport failure reason: " + "QUIC_NO_ERROR|Closed by application"); } else { EXPECT_EQ(response->body(), "upstream connect error or disconnect/reset before headers. reset " "reason: connection termination"); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 05bc3a7c38dc..f97ec8038bc4 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -4488,7 +4488,7 @@ TEST_P(ProtocolIntegrationTest, HandleUpstreamSocketFail) { if (upstreamProtocol() == Http::CodecType::HTTP3) { EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("upstream_reset_before_response_started{connection_termination|QUIC_" - "PACKET_WRITE_ERROR}")); + "PACKET_WRITE_ERROR|Write_failed_with_error:_9_(Bad_file_descriptor)}")); } else { EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("upstream_reset_before_response_started{connection_termination}")); @@ -4694,8 +4694,8 @@ TEST_P(ProtocolIntegrationTest, InvalidResponseHeaderName) { EXPECT_EQ("502", response->headers().getStatusValue()); test_server_->waitForCounterGe("http.config_test.downstream_rq_5xx", 1); if (upstreamProtocol() == Http::CodecType::HTTP3) { - EXPECT_EQ(waitForAccessLog(access_log_name_), - "upstream_reset_before_response_started{protocol_error|QUIC_HTTP_FRAME_ERROR}"); + EXPECT_EQ(waitForAccessLog(access_log_name_), "upstream_reset_before_response_started{protocol_" + "error|QUIC_HTTP_FRAME_ERROR|Invalid_headers}"); } else { EXPECT_EQ(waitForAccessLog(access_log_name_), "upstream_reset_before_response_started{protocol_error}"); From 266077f83cc75a570e87c407b5355266ded4344b Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Mon, 1 Jul 2024 07:15:46 -0700 Subject: [PATCH 19/31] formatter: removes unused getHostNameOrDefault (#34984) Previously getHostNameOrDefault was used to resolve HOSTNAME substitution, but now it's implemented via const absl::optional getHostname(), and getHostNameOrDefault hasn't been used since then. As a result, this would increase the test coverage of substitution_format_utility.cc. Risk Level: lo Testing: done Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Takeshi Yoneda --- source/common/formatter/substitution_format_utility.cc | 8 -------- source/common/formatter/substitution_format_utility.h | 1 - 2 files changed, 9 deletions(-) diff --git a/source/common/formatter/substitution_format_utility.cc b/source/common/formatter/substitution_format_utility.cc index 931474572342..1ec0b15cae98 100644 --- a/source/common/formatter/substitution_format_utility.cc +++ b/source/common/formatter/substitution_format_utility.cc @@ -69,14 +69,6 @@ const absl::optional SubstitutionFormatUtils::getHostname() { return hostname; } -const std::string SubstitutionFormatUtils::getHostnameOrDefault() { - absl::optional hostname = getHostname(); - if (hostname.has_value()) { - return hostname.value(); - } - return DefaultUnspecifiedValueString; -} - const ProtobufWkt::Value& SubstitutionFormatUtils::unspecifiedValue() { return ValueUtil::nullValue(); } diff --git a/source/common/formatter/substitution_format_utility.h b/source/common/formatter/substitution_format_utility.h index 959f326ebab8..b1176f4d0760 100644 --- a/source/common/formatter/substitution_format_utility.h +++ b/source/common/formatter/substitution_format_utility.h @@ -40,7 +40,6 @@ class SubstitutionFormatUtils { static const std::string& protocolToStringOrDefault(const absl::optional& protocol); static const absl::optional getHostname(); - static const std::string getHostnameOrDefault(); /** * Unspecified value for protobuf. From dd7d9b0326967e32aef6accd87b30bf7b9543f53 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 1 Jul 2024 17:06:21 +0200 Subject: [PATCH 20/31] changelogs: Add changelog for Datadog fix (#34931) Signed-off-by: Ryan Northey --- changelogs/current.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index d4fa242f22c0..67b65257be3f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -227,6 +227,9 @@ bug_fixes: change: | Fixed a bug where additional :ref:`cookie attributes ` are not sent properly to clients. +- area: datadog + change: | + Bumped the version of datadog to resolve a crashing bug in earlier versions of the library. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` From 37b801ecf1ab413815656b61b25bc97263125562 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Mon, 1 Jul 2024 21:04:12 -0700 Subject: [PATCH 21/31] Removes unused createStaticConfigProvider (#34985) Previously, a variant of createStaticConfigProvider taking Protobuf::Message as a first param was not used, and that was one reason for the low test coverage of envoy/config package. This removes it, and potentially allows the package to be removed from the coverage exception. Risk Level: low Testing: done Signed-off-by: Takeshi Yoneda --- envoy/config/config_provider_manager.h | 24 +---------- source/common/router/scoped_rds.h | 6 --- .../config/config_provider_impl_test.cc | 41 +++++++++++-------- test/common/router/scoped_rds_test.cc | 15 ------- test/mocks/config/mocks.h | 4 -- 5 files changed, 26 insertions(+), 64 deletions(-) diff --git a/envoy/config/config_provider_manager.h b/envoy/config/config_provider_manager.h index 06f181607e2a..cdb16a17ea6d 100644 --- a/envoy/config/config_provider_manager.h +++ b/envoy/config/config_provider_manager.h @@ -58,23 +58,6 @@ class ConfigProviderManager : public Singleton::Instance { Init::Manager& init_manager, const std::string& stat_prefix, const OptionalArg& optarg) PURE; - /** - * Returns a ConfigProvider associated with a statically specified configuration. - * @param config_proto supplies the configuration proto. - * @param factory_context is the context to use for the provider. - * @param optarg supplies an optional argument with data specific to the concrete class. - * @return ConfigProviderPtr a newly allocated static config provider. - */ - virtual ConfigProviderPtr - createStaticConfigProvider(const Protobuf::Message& config_proto, - Server::Configuration::ServerFactoryContext& factory_context, - const OptionalArg& optarg) { - UNREFERENCED_PARAMETER(config_proto); - UNREFERENCED_PARAMETER(factory_context); - UNREFERENCED_PARAMETER(optarg); - return nullptr; - } - /** * Returns a ConfigProvider associated with a statically specified configuration. This is intended * to be used when a set of configuration protos is required to build the full configuration. @@ -86,12 +69,7 @@ class ConfigProviderManager : public Singleton::Instance { virtual ConfigProviderPtr createStaticConfigProvider(ProtobufTypes::ConstMessagePtrVector&& config_protos, Server::Configuration::ServerFactoryContext& factory_context, - const OptionalArg& optarg) { - UNREFERENCED_PARAMETER(config_protos); - UNREFERENCED_PARAMETER(factory_context); - UNREFERENCED_PARAMETER(optarg); - return nullptr; - } + const OptionalArg& optarg) PURE; }; } // namespace Config diff --git a/source/common/router/scoped_rds.h b/source/common/router/scoped_rds.h index cbbec39cac2e..445fbca29763 100644 --- a/source/common/router/scoped_rds.h +++ b/source/common/router/scoped_rds.h @@ -284,12 +284,6 @@ class ScopedRoutesConfigProviderManager : public Envoy::Config::ConfigProviderMa Server::Configuration::ServerFactoryContext& factory_context, Init::Manager& init_manager, const std::string& stat_prefix, const Envoy::Config::ConfigProviderManager::OptionalArg& optarg) override; - Envoy::Config::ConfigProviderPtr - createStaticConfigProvider(const Protobuf::Message&, Server::Configuration::ServerFactoryContext&, - const Envoy::Config::ConfigProviderManager::OptionalArg&) override { - PANIC("SRDS supports delta updates and requires the use of the createStaticConfigProvider() " - "overload that accepts a config proto set as an argument."); - } Envoy::Config::ConfigProviderPtr createStaticConfigProvider( std::vector>&& config_protos, Server::Configuration::ServerFactoryContext& factory_context, diff --git a/test/common/config/config_provider_impl_test.cc b/test/common/config/config_provider_impl_test.cc index 47549b986b76..c1cfaa03f4a8 100644 --- a/test/common/config/config_provider_impl_test.cc +++ b/test/common/config/config_provider_impl_test.cc @@ -195,19 +195,14 @@ class DummyConfigProviderManager : public ConfigProviderManagerImplBase { // Envoy::Config::ConfigProviderManager ConfigProviderPtr - createStaticConfigProvider(const Protobuf::Message& config_proto, + createStaticConfigProvider(ProtobufTypes::ConstMessagePtrVector&& configs, Server::Configuration::ServerFactoryContext& factory_context, - const Envoy::Config::ConfigProviderManager::OptionalArg&) override { - return std::make_unique( - dynamic_cast(config_proto), factory_context, - *this); - } - ConfigProviderPtr - createStaticConfigProvider(std::vector>&&, - Server::Configuration::ServerFactoryContext&, const OptionalArg&) override { - ASSERT(false, "this provider does not expect multiple config protos"); - return nullptr; + // Currently, the dummy config provider only supports a single static config. We can extend this + // to support multiple static configs if needed. + ASSERT(configs.size() == 1); + auto config = dynamic_cast(*configs[0]); + return std::make_unique(config, factory_context, *this); } }; @@ -251,6 +246,13 @@ test::common::config::DummyConfig parseDummyConfigFromYaml(const std::string& ya return config; } +std::unique_ptr +parseDummyConfigPtrFromYaml(const std::string& yaml) { + auto config = std::make_unique(); + TestUtility::loadFromYaml(yaml, *config); + return config; +} + // Tests that dynamic config providers share ownership of the config // subscriptions, config protos and data structures generated as a result of the // configurations (i.e., the ConfigProvider::Config). @@ -431,12 +433,12 @@ TEST_F(ConfigProviderImplTest, ConfigDump) { EXPECT_EQ(expected_config_dump.DebugString(), dummy_config_dump.DebugString()); // Static config dump only. - std::string config_yaml = "a: a static dummy config"; timeSystem().setSystemTime(std::chrono::milliseconds(1234567891234)); + ProtobufTypes::ConstMessagePtrVector configs; + configs.push_back(parseDummyConfigPtrFromYaml("a: a static dummy config")); ConfigProviderPtr static_config = provider_manager_->createStaticConfigProvider( - parseDummyConfigFromYaml(config_yaml), server_factory_context_, - ConfigProviderManager::NullOptionalArg()); + std::move(configs), server_factory_context_, ConfigProviderManager::NullOptionalArg()); message_ptr = server_factory_context_.admin_.config_tracker_.config_tracker_callbacks_["dummy"]( Matchers::UniversalStringMatcher()); const auto& dummy_config_dump2 = @@ -481,9 +483,10 @@ TEST_F(ConfigProviderImplTest, ConfigDump) { expected_config_dump); EXPECT_EQ(expected_config_dump.DebugString(), dummy_config_dump3.DebugString()); + ProtobufTypes::ConstMessagePtrVector configs2; + configs2.push_back(parseDummyConfigPtrFromYaml("a: another static dummy config")); ConfigProviderPtr static_config2 = provider_manager_->createStaticConfigProvider( - parseDummyConfigFromYaml("a: another static dummy config"), server_factory_context_, - ConfigProviderManager::NullOptionalArg()); + std::move(configs2), server_factory_context_, ConfigProviderManager::NullOptionalArg()); message_ptr = server_factory_context_.admin_.config_tracker_.config_tracker_callbacks_["dummy"]( Matchers::UniversalStringMatcher()); const auto& dummy_config_dump4 = @@ -665,6 +668,12 @@ class DeltaDummyConfigProviderManager : public ConfigProviderManagerImplBase { return std::make_unique(std::move(subscription)); } + + ConfigProviderPtr createStaticConfigProvider(ProtobufTypes::ConstMessagePtrVector&&, + Server::Configuration::ServerFactoryContext&, + const OptionalArg&) override { + return nullptr; + }; }; DeltaDummyConfigSubscription::DeltaDummyConfigSubscription( diff --git a/test/common/router/scoped_rds_test.cc b/test/common/router/scoped_rds_test.cc index bc11aeae7e8b..928073348adf 100644 --- a/test/common/router/scoped_rds_test.cc +++ b/test/common/router/scoped_rds_test.cc @@ -1218,21 +1218,6 @@ route_configuration_name: dynamic-foo-route-config EXPECT_THAT(expected_config_dump, ProtoEq(scoped_routes_config_dump7)); } -TEST_F(ScopedRdsTest, DeltaStaticConfigProviderOnly) { - // Use match all regex due to lack of distinctive matchable output for - // coverage test. - EXPECT_DEATH(config_provider_manager_->createStaticConfigProvider( - parseScopedRouteConfigurationFromYaml(R"EOF( -name: dynamic-foo -route_configuration_name: static-foo-route-config -key: - fragments: { string_key: "172.30.30.10" } -)EOF"), - server_factory_context_, - Envoy::Config::ConfigProviderManager::NullOptionalArg()), - ".*"); -} - // Tests whether scope key conflict with updated scopes is ignored. TEST_F(ScopedRdsTest, IgnoreConflictWithUpdatedScopeDelta) { setup(); diff --git a/test/mocks/config/mocks.h b/test/mocks/config/mocks.h index 556ce0f7c0ff..f24e28806b51 100644 --- a/test/mocks/config/mocks.h +++ b/test/mocks/config/mocks.h @@ -157,10 +157,6 @@ class MockConfigProviderManager : public ConfigProviderManager { Server::Configuration::ServerFactoryContext& factory_context, Init::Manager& init_manager, const std::string& stat_prefix, const Envoy::Config::ConfigProviderManager::OptionalArg& optarg)); - MOCK_METHOD(ConfigProviderPtr, createStaticConfigProvider, - (const Protobuf::Message& config_proto, - Server::Configuration::ServerFactoryContext& factory_context, - const Envoy::Config::ConfigProviderManager::OptionalArg& optarg)); MOCK_METHOD(ConfigProviderPtr, createStaticConfigProvider, (std::vector> && config_protos, Server::Configuration::ServerFactoryContext& factory_context, From ee075b7c2a5f8449bfc17fbab94010696d3ed052 Mon Sep 17 00:00:00 2001 From: Arul Thileeban Sagayam Date: Tue, 2 Jul 2024 00:42:50 -0400 Subject: [PATCH 22/31] jwt_authn: Add functionality to remove query parameter containing JWT (#34418) Setting forward as false in JWT Authn filter config removes the JWT from headers, but doesn't remove JWT from query params or cookies. This change adds functionality to remove query parameters based on forward config Risk Level: Low Testing: Unit Testing Signed-off-by: Arul Thileeban Sagayam --- .../filters/http/jwt_authn/v3/config.proto | 2 +- changelogs/current.yaml | 7 +++ source/common/runtime/runtime_features.cc | 1 + .../extensions/filters/http/jwt_authn/BUILD | 1 + .../filters/http/jwt_authn/authenticator.cc | 6 +-- .../filters/http/jwt_authn/authenticator.h | 2 +- .../filters/http/jwt_authn/extractor.cc | 31 +++++++++---- .../filters/http/jwt_authn/extractor.h | 4 +- .../filters/http/jwt_authn/verifier.h | 2 +- test/extensions/filters/http/jwt_authn/BUILD | 1 + .../filters/http/jwt_authn/extractor_test.cc | 45 ++++++++++++++++++- .../http/jwt_authn/group_verifier_test.cc | 28 ++++++------ test/extensions/filters/http/jwt_authn/mock.h | 6 +-- 13 files changed, 102 insertions(+), 34 deletions(-) diff --git a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 18cd08c5c9cb..41d694a014dd 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -191,7 +191,7 @@ message JwtProvider { // If false, the JWT is removed in the request after a success verification. If true, the JWT is // not removed in the request. Default value is false. - // caveat: only works for from_header & has no effect for JWTs extracted through from_params & from_cookies. + // caveat: only works for from_header/from_params & has no effect for JWTs extracted through from_cookies. bool forward = 5; // Two fields below define where to extract the JWT from an HTTP request. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 67b65257be3f..a536f0e13030 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -130,6 +130,13 @@ minor_behavior_changes: change: | Changing HTTP/2 semi-colon prefixed headers to being sanitized by Envoy code rather than nghttp2. Should be a functional no-op but guarded by ``envoy.reloadable_features.sanitize_http2_headers_without_nghttp2``. +- area: jwt_authn + change: | + Changes the behavior of the + :ref:`forward ` + config. Previously, the config only removes JWT if set in headers. With this addition, the config can also be + used to remove JWT set in query parameters. This behavior can be reverted by setting the runtime guard + ``envoy.reloadable_features.jwt_authn_remove_jwt_from_query_params`` to false. 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 867cfd642b3b..bcd7edac129e 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -64,6 +64,7 @@ RUNTIME_GUARD(envoy_reloadable_features_http_filter_avoid_reentrant_local_reply) RUNTIME_GUARD(envoy_reloadable_features_http_reject_path_with_fragment); RUNTIME_GUARD(envoy_reloadable_features_http_route_connect_proxy_by_default); RUNTIME_GUARD(envoy_reloadable_features_immediate_response_use_filter_mutation_rule); +RUNTIME_GUARD(envoy_reloadable_features_jwt_authn_remove_jwt_from_query_params); RUNTIME_GUARD(envoy_reloadable_features_jwt_authn_validate_uri); RUNTIME_GUARD(envoy_reloadable_features_no_downgrade_to_canonical_name); RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name); diff --git a/source/extensions/filters/http/jwt_authn/BUILD b/source/extensions/filters/http/jwt_authn/BUILD index 915011c82f4b..ccf0af7f35ba 100644 --- a/source/extensions/filters/http/jwt_authn/BUILD +++ b/source/extensions/filters/http/jwt_authn/BUILD @@ -14,6 +14,7 @@ envoy_cc_library( srcs = ["extractor.cc"], hdrs = ["extractor.h"], deps = [ + "//envoy/runtime:runtime_interface", "//source/common/http:header_utility_lib", "//source/common/http:utility_lib", "@com_google_absl//absl/container:btree", diff --git a/source/extensions/filters/http/jwt_authn/authenticator.cc b/source/extensions/filters/http/jwt_authn/authenticator.cc index 237f147ee5d0..321376a6af0e 100644 --- a/source/extensions/filters/http/jwt_authn/authenticator.cc +++ b/source/extensions/filters/http/jwt_authn/authenticator.cc @@ -60,7 +60,7 @@ class AuthenticatorImpl : public Logger::Loggable, void onJwksSuccess(google::jwt_verify::JwksPtr&& jwks) override; void onJwksError(Failure reason) override; // Following functions are for Authenticator interface. - void verify(Http::HeaderMap& headers, Tracing::Span& parent_span, + void verify(Http::RequestHeaderMap& headers, Tracing::Span& parent_span, std::vector&& tokens, SetExtractedJwtDataCallback set_extracted_jwt_data_cb, AuthenticatorCallback callback, ClearRouteCacheCallback clear_route_cb) override; @@ -111,7 +111,7 @@ class AuthenticatorImpl : public Logger::Loggable, // The JWKS data object JwksCache::JwksData* jwks_data_{}; // The HTTP request headers - Http::HeaderMap* headers_{}; + Http::RequestHeaderMap* headers_{}; // The active span for the request Tracing::Span* parent_span_{&Tracing::NullSpan::instance()}; // The callback function called to set the extracted payload and header from a verified JWT. @@ -145,7 +145,7 @@ std::string AuthenticatorImpl::name() const { return "_UNKNOWN_"; } -void AuthenticatorImpl::verify(Http::HeaderMap& headers, Tracing::Span& parent_span, +void AuthenticatorImpl::verify(Http::RequestHeaderMap& headers, Tracing::Span& parent_span, std::vector&& tokens, SetExtractedJwtDataCallback set_extracted_jwt_data_cb, AuthenticatorCallback callback, diff --git a/source/extensions/filters/http/jwt_authn/authenticator.h b/source/extensions/filters/http/jwt_authn/authenticator.h index be5809aa0ba1..d54157a472f4 100644 --- a/source/extensions/filters/http/jwt_authn/authenticator.h +++ b/source/extensions/filters/http/jwt_authn/authenticator.h @@ -33,7 +33,7 @@ class Authenticator { // Verify if headers satisfies the JWT requirements. Can be limited to single provider with // extract_param. - virtual void verify(Http::HeaderMap& headers, Tracing::Span& parent_span, + virtual void verify(Http::RequestHeaderMap& headers, Tracing::Span& parent_span, std::vector&& tokens, SetExtractedJwtDataCallback set_extracted_jwt_data_cb, AuthenticatorCallback callback, ClearRouteCacheCallback clear_route_cb) PURE; diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index e81893371e0b..9d088297b26e 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -8,6 +8,7 @@ #include "source/common/http/header_utility.h" #include "source/common/http/headers.h" #include "source/common/http/utility.h" +#include "source/common/runtime/runtime_features.h" #include "source/common/singleton/const_singleton.h" #include "absl/container/btree_map.h" @@ -92,7 +93,7 @@ class JwtHeaderLocation : public JwtLocationBase { const LowerCaseString& header) : JwtLocationBase(token, issuer_checker), header_(header) {} - void removeJwt(Http::HeaderMap& headers) const override { headers.remove(header_); } + void removeJwt(Http::RequestHeaderMap& headers) const override { headers.remove(header_); } private: // the header name the JWT is extracted from. @@ -103,12 +104,26 @@ class JwtHeaderLocation : public JwtLocationBase { class JwtParamLocation : public JwtLocationBase { public: JwtParamLocation(const std::string& token, const JwtIssuerChecker& issuer_checker, - const std::string&) - : JwtLocationBase(token, issuer_checker) {} + const std::string& param) + : JwtLocationBase(token, issuer_checker), param_(param) {} + + void removeJwt(Http::RequestHeaderMap& headers) const override { + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.jwt_authn_remove_jwt_from_query_params")) { + absl::string_view path = headers.getPathValue(); + Http::Utility::QueryParamsMulti query_params = + Http::Utility::QueryParamsMulti::parseAndDecodeQueryString(path); + + query_params.remove(param_); - void removeJwt(Http::HeaderMap&) const override { - // TODO(qiwzhang): remove JWT from parameter. + const auto updated_path = query_params.replaceQueryString(headers.Path()->value()); + headers.setPath(updated_path); + } } + +private: + // the param name the JWT is extracted from. + const std::string& param_; }; // The JwtLocation for cookie extraction. @@ -117,7 +132,7 @@ class JwtCookieLocation : public JwtLocationBase { JwtCookieLocation(const std::string& token, const JwtIssuerChecker& issuer_checker) : JwtLocationBase(token, issuer_checker) {} - void removeJwt(Http::HeaderMap&) const override { + void removeJwt(Http::RequestHeaderMap&) const override { // TODO(theshubhamp): remove JWT from cookies. } }; @@ -136,7 +151,7 @@ class ExtractorImpl : public Logger::Loggable, public Extractor std::vector extract(const Http::RequestHeaderMap& headers) const override; - void sanitizeHeaders(Http::HeaderMap& headers) const override; + void sanitizeHeaders(Http::RequestHeaderMap& headers) const override; private: // add a header config @@ -332,7 +347,7 @@ absl::string_view ExtractorImpl::extractJWT(absl::string_view value_str, return value_str.substr(starting); } -void ExtractorImpl::sanitizeHeaders(Http::HeaderMap& headers) const { +void ExtractorImpl::sanitizeHeaders(Http::RequestHeaderMap& headers) const { for (const auto& header : headers_to_sanitize_) { headers.remove(header); } diff --git a/source/extensions/filters/http/jwt_authn/extractor.h b/source/extensions/filters/http/jwt_authn/extractor.h index c737e31f1faa..9c6dd2b06020 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.h +++ b/source/extensions/filters/http/jwt_authn/extractor.h @@ -32,7 +32,7 @@ class JwtLocation { virtual bool isIssuerAllowed(const std::string& issuer) const PURE; // Remove the token from the headers - virtual void removeJwt(Http::HeaderMap& headers) const PURE; + virtual void removeJwt(Http::RequestHeaderMap& headers) const PURE; }; using JwtLocationConstPtr = std::unique_ptr; @@ -81,7 +81,7 @@ class Extractor { * * @param headers is the HTTP request headers. */ - virtual void sanitizeHeaders(Http::HeaderMap& headers) const PURE; + virtual void sanitizeHeaders(Http::RequestHeaderMap& headers) const PURE; /** * Create an instance of Extractor for a given config. diff --git a/source/extensions/filters/http/jwt_authn/verifier.h b/source/extensions/filters/http/jwt_authn/verifier.h index dd775caf1d6f..26a6964dac8e 100644 --- a/source/extensions/filters/http/jwt_authn/verifier.h +++ b/source/extensions/filters/http/jwt_authn/verifier.h @@ -57,7 +57,7 @@ class Verifier { * * @return the request headers. */ - virtual Http::HeaderMap& headers() const PURE; + virtual Http::RequestHeaderMap& headers() const PURE; /** * Returns the active span wrapped in this context. diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index 24e5e3faac22..2603b1f91c02 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -38,6 +38,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/jwt_authn:extractor_lib", "//test/extensions/filters/http/jwt_authn:test_common_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/jwt_authn/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index 3cad76fe54a9..75109af4fcb1 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -1,9 +1,11 @@ #include "envoy/extensions/filters/http/jwt_authn/v3/config.pb.h" +#include "source/common/http/utility.h" #include "source/common/protobuf/utility.h" #include "source/extensions/filters/http/jwt_authn/extractor.h" #include "test/extensions/filters/http/jwt_authn/test_common.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication; @@ -202,7 +204,27 @@ TEST_F(ExtractorTest, TestDefaultParamLocation) { EXPECT_FALSE(tokens[0]->isIssuerAllowed("issuer5")); EXPECT_FALSE(tokens[0]->isIssuerAllowed("unknown_issuer")); - tokens[0]->removeJwt(headers); + // Test token remove from the query parameter + { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.jwt_authn_remove_jwt_from_query_params", "false"}}); + + tokens[0]->removeJwt(headers); + Http::Utility::QueryParamsMulti query_params = + Http::Utility::QueryParamsMulti::parseAndDecodeQueryString(headers.getPathValue()); + EXPECT_EQ(query_params.getFirstValue("access_token").has_value(), true); + } + { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.jwt_authn_remove_jwt_from_query_params", "true"}}); + + tokens[0]->removeJwt(headers); + Http::Utility::QueryParamsMulti query_params = + Http::Utility::QueryParamsMulti::parseAndDecodeQueryString(headers.getPathValue()); + EXPECT_EQ(query_params.getFirstValue("access_token").has_value(), false); + } } // Test extracting token from the custom header: "token-header" @@ -319,7 +341,26 @@ TEST_F(ExtractorTest, TestCustomParamToken) { EXPECT_FALSE(tokens[0]->isIssuerAllowed("issuer5")); EXPECT_FALSE(tokens[0]->isIssuerAllowed("unknown_issuer")); - tokens[0]->removeJwt(headers); + { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.jwt_authn_remove_jwt_from_query_params", "false"}}); + + tokens[0]->removeJwt(headers); + Http::Utility::QueryParamsMulti query_params = + Http::Utility::QueryParamsMulti::parseAndDecodeQueryString(headers.getPathValue()); + EXPECT_EQ(query_params.getFirstValue("token_param").has_value(), true); + } + { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.jwt_authn_remove_jwt_from_query_params", "true"}}); + + tokens[0]->removeJwt(headers); + Http::Utility::QueryParamsMulti query_params = + Http::Utility::QueryParamsMulti::parseAndDecodeQueryString(headers.getPathValue()); + EXPECT_EQ(query_params.getFirstValue("token_param").has_value(), false); + } } // Test extracting token from a cookie diff --git a/test/extensions/filters/http/jwt_authn/group_verifier_test.cc b/test/extensions/filters/http/jwt_authn/group_verifier_test.cc index 6a3ab998edf8..a54a747481c5 100644 --- a/test/extensions/filters/http/jwt_authn/group_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/group_verifier_test.cc @@ -82,16 +82,17 @@ class GroupVerifierTest : public testing::Test { for (const auto& it : statuses) { auto mock_auth = std::make_unique(); EXPECT_CALL(*mock_auth, doVerify(_, _, _, _, _)) - .WillOnce(Invoke([issuer = it.first, status = it.second]( - Http::HeaderMap&, Tracing::Span&, std::vector*, - SetExtractedJwtDataCallback set_extracted_jwt_data_cb, - AuthenticatorCallback callback) { - if (status == Status::Ok) { - ProtobufWkt::Struct empty_struct; - set_extracted_jwt_data_cb(issuer, empty_struct); - } - callback(status); - })); + .WillOnce( + Invoke([issuer = it.first, status = it.second]( + Http::RequestHeaderMap&, Tracing::Span&, std::vector*, + SetExtractedJwtDataCallback set_extracted_jwt_data_cb, + AuthenticatorCallback callback) { + if (status == Status::Ok) { + ProtobufWkt::Struct empty_struct; + set_extracted_jwt_data_cb(issuer, empty_struct); + } + callback(status); + })); EXPECT_CALL(*mock_auth, onDestroy()); mock_auths_[it.first] = std::move(mock_auth); } @@ -114,9 +115,10 @@ class GroupVerifierTest : public testing::Test { for (const auto& provider : providers) { auto mock_auth = std::make_unique(); EXPECT_CALL(*mock_auth, doVerify(_, _, _, _, _)) - .WillOnce(Invoke([&, iss = provider]( - Http::HeaderMap&, Tracing::Span&, std::vector*, - SetExtractedJwtDataCallback, AuthenticatorCallback callback) { + .WillOnce(Invoke([&, iss = provider](Http::RequestHeaderMap&, Tracing::Span&, + std::vector*, + SetExtractedJwtDataCallback, + AuthenticatorCallback callback) { callbacks_[iss] = std::move(callback); })); EXPECT_CALL(*mock_auth, onDestroy()); diff --git a/test/extensions/filters/http/jwt_authn/mock.h b/test/extensions/filters/http/jwt_authn/mock.h index 3167fcd67a08..aa963462b8e5 100644 --- a/test/extensions/filters/http/jwt_authn/mock.h +++ b/test/extensions/filters/http/jwt_authn/mock.h @@ -29,12 +29,12 @@ class MockAuthFactory : public AuthFactory { class MockAuthenticator : public Authenticator { public: MOCK_METHOD(void, doVerify, - (Http::HeaderMap & headers, Tracing::Span& parent_span, + (Http::RequestHeaderMap & headers, Tracing::Span& parent_span, std::vector* tokens, SetExtractedJwtDataCallback set_extracted_jwt_data_cb, AuthenticatorCallback callback)); - void verify(Http::HeaderMap& headers, Tracing::Span& parent_span, + void verify(Http::RequestHeaderMap& headers, Tracing::Span& parent_span, std::vector&& tokens, SetExtractedJwtDataCallback set_extracted_jwt_data_cb, AuthenticatorCallback callback, ClearRouteCacheCallback) override { @@ -61,7 +61,7 @@ class MockExtractor : public Extractor { public: MOCK_METHOD(std::vector, extract, (const Http::RequestHeaderMap& headers), (const)); - MOCK_METHOD(void, sanitizeHeaders, (Http::HeaderMap & headers), (const)); + MOCK_METHOD(void, sanitizeHeaders, (Http::RequestHeaderMap & headers), (const)); }; class MockJwtCache : public JwtCache { From 85cfc785038e15b4d0231aa8e734c144545a2602 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 2 Jul 2024 01:49:33 -0400 Subject: [PATCH 23/31] cluster: moving towards no exceptions (#34896) Risk Level: low Testing: updated tests Docs Changes: n/a Release Notes: n/a envoyproxy/envoy-mobile#176 Signed-off-by: Alyssa Wilk --- source/common/upstream/upstream_impl.cc | 49 +-- source/common/upstream/upstream_impl.h | 2 +- .../extensions/clusters/aggregate/cluster.cc | 10 +- .../extensions/clusters/aggregate/cluster.h | 16 +- .../clusters/dynamic_forward_proxy/cluster.cc | 13 +- .../clusters/dynamic_forward_proxy/cluster.h | 13 +- source/extensions/clusters/eds/eds.cc | 20 +- source/extensions/clusters/eds/eds.h | 9 +- .../logical_dns/logical_dns_cluster.cc | 15 +- .../logical_dns/logical_dns_cluster.h | 8 +- .../original_dst/original_dst_cluster.cc | 10 +- .../original_dst/original_dst_cluster.h | 11 +- .../clusters/redis/redis_cluster.cc | 41 ++- .../extensions/clusters/redis/redis_cluster.h | 24 +- .../clusters/static/static_cluster.cc | 17 +- .../clusters/static/static_cluster.h | 7 +- .../clusters/strict_dns/strict_dns_cluster.cc | 26 +- .../clusters/strict_dns/strict_dns_cluster.h | 11 +- .../upstream/cluster_factory_impl_test.cc | 6 +- test/common/upstream/upstream_impl_test.cc | 337 +++++++++--------- .../clusters/aggregate/cluster_test.cc | 5 +- .../dynamic_forward_proxy/cluster_test.cc | 6 +- .../extensions/clusters/eds/eds_speed_test.cc | 2 +- test/extensions/clusters/eds/eds_test.cc | 6 +- .../logical_dns/logical_dns_cluster_test.cc | 4 +- .../clusters/redis/redis_cluster_test.cc | 4 +- .../clusters/custom_static_cluster.h | 16 +- 27 files changed, 413 insertions(+), 275 deletions(-) diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 470568a5090d..052696fe8d36 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -115,7 +115,8 @@ createProtocolOptionsConfig(const std::string& name, const ProtobufWkt::Any& typ return factory->createProtocolOptionsConfig(*proto_config, factory_context); } -absl::flat_hash_map parseExtensionProtocolOptions( +absl::StatusOr> +parseExtensionProtocolOptions( const envoy::config::cluster::v3::Cluster& config, Server::Configuration::ProtocolOptionsFactoryContext& factory_context) { absl::flat_hash_map options; @@ -123,7 +124,7 @@ absl::flat_hash_map parseExten for (const auto& it : config.typed_extension_protocol_options()) { auto& name = it.first; auto object_or_error = createProtocolOptionsConfig(name, it.second, factory_context); - THROW_IF_STATUS_NOT_OK(object_or_error, throw); + RETURN_IF_STATUS_NOT_OK(object_or_error); if (object_or_error.value() != nullptr) { options[name] = std::move(object_or_error.value()); } @@ -1050,6 +1051,9 @@ LegacyLbPolicyConfigHelper::getTypedLbConfigFromLegacyProto( return getTypedLbConfigFromLegacyProtoWithoutSubset(cluster, visitor); } +using ProtocolOptionsHashMap = + absl::flat_hash_map; + ClusterInfoImpl::ClusterInfoImpl( Init::Manager& init_manager, Server::Configuration::ServerFactoryContext& server_context, const envoy::config::cluster::v3::Cluster& config, @@ -1065,7 +1069,8 @@ ClusterInfoImpl::ClusterInfoImpl( config.has_eds_cluster_config() ? std::make_unique(config.eds_cluster_config().service_name()) : nullptr), - extension_protocol_options_(parseExtensionProtocolOptions(config, factory_context)), + extension_protocol_options_(THROW_OR_RETURN_VALUE( + parseExtensionProtocolOptions(config, factory_context), ProtocolOptionsHashMap)), http_protocol_options_(THROW_OR_RETURN_VALUE( createOptions(config, extensionProtocolOptionsTyped( @@ -1454,7 +1459,8 @@ absl::StatusOr validateTransportSocketSupportsQuic( } ClusterImplBase::ClusterImplBase(const envoy::config::cluster::v3::Cluster& cluster, - ClusterFactoryContext& cluster_context) + ClusterFactoryContext& cluster_context, + absl::Status& creation_status) : init_manager_(fmt::format("Cluster {}", cluster.name())), init_watcher_("ClusterImplBase", [this]() { onInitDone(); }), runtime_(cluster_context.serverFactoryContext().runtime()), @@ -1476,14 +1482,14 @@ ClusterImplBase::ClusterImplBase(const envoy::config::cluster::v3::Cluster& clus transport_factory_context_->setInitManager(init_manager_); auto socket_factory_or_error = createTransportSocketFactory(cluster, *transport_factory_context_); - THROW_IF_NOT_OK(socket_factory_or_error.status()); + SET_AND_RETURN_IF_NOT_OK(socket_factory_or_error.status(), creation_status); auto* raw_factory_pointer = socket_factory_or_error.value().get(); - auto socket_matcher = - THROW_OR_RETURN_VALUE(TransportSocketMatcherImpl::create( - cluster.transport_socket_matches(), *transport_factory_context_, - socket_factory_or_error.value(), *stats_scope), - std::unique_ptr); + auto socket_matcher_or_error = TransportSocketMatcherImpl::create( + cluster.transport_socket_matches(), *transport_factory_context_, + socket_factory_or_error.value(), *stats_scope); + SET_AND_RETURN_IF_NOT_OK(socket_matcher_or_error.status(), creation_status); + auto socket_matcher = std::move(*socket_matcher_or_error); const bool matcher_supports_alpn = socket_matcher->allMatchesSupportAlpn(); auto& dispatcher = server_context.mainThreadDispatcher(); info_ = std::shared_ptr( @@ -1499,28 +1505,34 @@ ClusterImplBase::ClusterImplBase(const envoy::config::cluster::v3::Cluster& clus if ((info_->features() & ClusterInfoImpl::Features::USE_ALPN)) { if (!raw_factory_pointer->supportsAlpn()) { - throwEnvoyExceptionOrPanic( + creation_status = absl::InvalidArgumentError( fmt::format("ALPN configured for cluster {} which has a non-ALPN transport socket: {}", cluster.name(), cluster.DebugString())); + return; } if (!matcher_supports_alpn && !runtime_.snapshot().featureEnabled(ClusterImplBase::DoNotValidateAlpnRuntimeKey, 0)) { - throwEnvoyExceptionOrPanic(fmt::format( + creation_status = absl::InvalidArgumentError(fmt::format( "ALPN configured for cluster {} which has a non-ALPN transport socket matcher: {}", cluster.name(), cluster.DebugString())); + return; } } if (info_->features() & ClusterInfoImpl::Features::HTTP3) { #if defined(ENVOY_ENABLE_QUIC) - if (!THROW_OR_RETURN_VALUE(validateTransportSocketSupportsQuic(cluster.transport_socket()), - bool)) { - throwEnvoyExceptionOrPanic( + absl::StatusOr supports_quic = + validateTransportSocketSupportsQuic(cluster.transport_socket()); + SET_AND_RETURN_IF_NOT_OK(supports_quic.status(), creation_status); + if (!*supports_quic) { + creation_status = absl::InvalidArgumentError( fmt::format("HTTP3 requires a QuicUpstreamTransport transport socket: {} {}", cluster.name(), cluster.transport_socket().DebugString())); + return; } #else - throwEnvoyExceptionOrPanic("HTTP3 configured but not enabled in the build."); + creation_status = absl::InvalidArgumentError("HTTP3 configured but not enabled in the build."); + return; #endif } @@ -1550,10 +1562,7 @@ ClusterImplBase::ClusterImplBase(const envoy::config::cluster::v3::Cluster& clus return absl::OkStatus(); }); // Drop overload configuration parsing. - absl::Status status = parseDropOverloadConfig(cluster.load_assignment()); - if (!status.ok()) { - throwEnvoyExceptionOrPanic(std::string(status.message())); - } + SET_AND_RETURN_IF_NOT_OK(parseDropOverloadConfig(cluster.load_assignment()), creation_status); } namespace { diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index f3711b3e78dd..275f492b5440 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -1186,7 +1186,7 @@ class ClusterImplBase : public Cluster, protected Logger::Loggable(config.clusters().begin(), config.clusters().end())) {} @@ -212,7 +213,10 @@ ClusterFactory::createClusterWithConfig( const envoy::config::cluster::v3::Cluster& cluster, const envoy::extensions::clusters::aggregate::v3::ClusterConfig& proto_config, Upstream::ClusterFactoryContext& context) { - auto new_cluster = std::make_shared(cluster, proto_config, context); + absl::Status creation_status = absl::OkStatus(); + auto new_cluster = + std::shared_ptr(new Cluster(cluster, proto_config, context, creation_status)); + RETURN_IF_NOT_OK(creation_status); auto lb = std::make_unique(*new_cluster); return std::make_pair(new_cluster, std::move(lb)); } diff --git a/source/extensions/clusters/aggregate/cluster.h b/source/extensions/clusters/aggregate/cluster.h index e691465b83c6..ac81421962b8 100644 --- a/source/extensions/clusters/aggregate/cluster.h +++ b/source/extensions/clusters/aggregate/cluster.h @@ -39,10 +39,6 @@ using ClusterSetConstSharedPtr = std::shared_ptr; class Cluster : public Upstream::ClusterImplBase { public: - Cluster(const envoy::config::cluster::v3::Cluster& cluster, - const envoy::extensions::clusters::aggregate::v3::ClusterConfig& config, - Upstream::ClusterFactoryContext& context); - // Upstream::Cluster Upstream::Cluster::InitializePhase initializePhase() const override { return Upstream::Cluster::InitializePhase::Secondary; @@ -53,7 +49,15 @@ class Cluster : public Upstream::ClusterImplBase { Random::RandomGenerator& random_; const ClusterSetConstSharedPtr clusters_; +protected: + Cluster(const envoy::config::cluster::v3::Cluster& cluster, + const envoy::extensions::clusters::aggregate::v3::ClusterConfig& config, + Upstream::ClusterFactoryContext& context, absl::Status& creation_status); + private: + friend class ClusterFactory; + friend class AggregateClusterTest; + // Upstream::ClusterImplBase void startPreInit() override { onPreInitComplete(); } }; @@ -64,6 +68,7 @@ class AggregateClusterLoadBalancer : public Upstream::LoadBalancer, Upstream::ClusterUpdateCallbacks, Logger::Loggable { public: + friend class AggregateLoadBalancerFactory; AggregateClusterLoadBalancer(const Upstream::ClusterInfoConstSharedPtr& parent_info, Upstream::ClusterManager& cluster_manager, Runtime::Loader& runtime, Random::RandomGenerator& random, @@ -137,7 +142,8 @@ class AggregateClusterLoadBalancer : public Upstream::LoadBalancer, // Load balancer factory created by the main thread and will be called in each worker thread to // create the thread local load balancer. -struct AggregateLoadBalancerFactory : public Upstream::LoadBalancerFactory { +class AggregateLoadBalancerFactory : public Upstream::LoadBalancerFactory { +public: AggregateLoadBalancerFactory(const Cluster& cluster) : cluster_(cluster) {} // Upstream::LoadBalancerFactory Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams) override { diff --git a/source/extensions/clusters/dynamic_forward_proxy/cluster.cc b/source/extensions/clusters/dynamic_forward_proxy/cluster.cc index e971d0ca3068..d80ca3e83eac 100644 --- a/source/extensions/clusters/dynamic_forward_proxy/cluster.cc +++ b/source/extensions/clusters/dynamic_forward_proxy/cluster.cc @@ -56,8 +56,9 @@ Cluster::Cluster( Extensions::Common::DynamicForwardProxy::DnsCacheSharedPtr&& cache, const envoy::extensions::clusters::dynamic_forward_proxy::v3::ClusterConfig& config, Upstream::ClusterFactoryContext& context, - Extensions::Common::DynamicForwardProxy::DnsCacheManagerSharedPtr&& cache_manager) - : Upstream::BaseDynamicClusterImpl(cluster, context), + Extensions::Common::DynamicForwardProxy::DnsCacheManagerSharedPtr&& cache_manager, + absl::Status& creation_status) + : Upstream::BaseDynamicClusterImpl(cluster, context, creation_status), dns_cache_manager_(std::move(cache_manager)), dns_cache_(std::move(cache)), update_callbacks_handle_(dns_cache_->addUpdateCallbacks(*this)), local_info_(context.serverFactoryContext().localInfo()), @@ -500,9 +501,11 @@ ClusterFactory::createClusterWithConfig( auto dns_cache_or_error = cache_manager->getCache(proto_config.dns_cache_config()); RETURN_IF_STATUS_NOT_OK(dns_cache_or_error); - auto new_cluster = - std::shared_ptr(new Cluster(cluster_config, std::move(dns_cache_or_error.value()), - proto_config, context, std::move(cache_manager))); + absl::Status creation_status = absl::OkStatus(); + auto new_cluster = std::shared_ptr( + new Cluster(cluster_config, std::move(dns_cache_or_error.value()), proto_config, context, + std::move(cache_manager), creation_status)); + RETURN_IF_NOT_OK(creation_status); Extensions::Common::DynamicForwardProxy::DFPClusterStoreFactory cluster_store_factory( context.serverFactoryContext().singletonManager()); diff --git a/source/extensions/clusters/dynamic_forward_proxy/cluster.h b/source/extensions/clusters/dynamic_forward_proxy/cluster.h index e2221ac31266..edc87f9c357b 100644 --- a/source/extensions/clusters/dynamic_forward_proxy/cluster.h +++ b/source/extensions/clusters/dynamic_forward_proxy/cluster.h @@ -54,15 +54,18 @@ class Cluster : public Upstream::BaseDynamicClusterImpl, bool touch(const std::string& cluster_name) override; void checkIdleSubCluster(); -private: - friend class ClusterFactory; - friend class ClusterTest; - +protected: Cluster(const envoy::config::cluster::v3::Cluster& cluster, Extensions::Common::DynamicForwardProxy::DnsCacheSharedPtr&& cacahe, const envoy::extensions::clusters::dynamic_forward_proxy::v3::ClusterConfig& config, Upstream::ClusterFactoryContext& context, - Extensions::Common::DynamicForwardProxy::DnsCacheManagerSharedPtr&& cache_manager); + Extensions::Common::DynamicForwardProxy::DnsCacheManagerSharedPtr&& cache_manager, + absl::Status& creation_status); + +private: + friend class ClusterFactory; + friend class ClusterTest; + struct ClusterInfo { ClusterInfo(std::string cluster_name, Cluster& parent); void touch(); diff --git a/source/extensions/clusters/eds/eds.cc b/source/extensions/clusters/eds/eds.cc index 02a2b748298d..bf245268d814 100644 --- a/source/extensions/clusters/eds/eds.cc +++ b/source/extensions/clusters/eds/eds.cc @@ -14,9 +14,20 @@ namespace Envoy { namespace Upstream { +absl::StatusOr> +EdsClusterImpl::create(const envoy::config::cluster::v3::Cluster& cluster, + ClusterFactoryContext& cluster_context) { + absl::Status creation_status = absl::OkStatus(); + std::unique_ptr ret = + absl::WrapUnique(new EdsClusterImpl(cluster, cluster_context, creation_status)); + RETURN_IF_NOT_OK(creation_status); + return ret; +} + EdsClusterImpl::EdsClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, - ClusterFactoryContext& cluster_context) - : BaseDynamicClusterImpl(cluster, cluster_context), + ClusterFactoryContext& cluster_context, + absl::Status& creation_status) + : BaseDynamicClusterImpl(cluster, cluster_context, creation_status), Envoy::Config::SubscriptionBase( cluster_context.messageValidationVisitor(), "cluster_name"), local_info_(cluster_context.serverFactoryContext().localInfo()), @@ -462,7 +473,10 @@ EdsClusterFactory::createClusterImpl(const envoy::config::cluster::v3::Cluster& return absl::InvalidArgumentError("cannot create an EDS cluster without an EDS config"); } - return std::make_pair(std::make_unique(cluster, context), nullptr); + absl::StatusOr> cluster_or_error = + EdsClusterImpl::create(cluster, context); + RETURN_IF_NOT_OK(cluster_or_error.status()); + return std::make_pair(std::move(*cluster_or_error), nullptr); } bool EdsClusterImpl::validateAllLedsUpdated() const { diff --git a/source/extensions/clusters/eds/eds.h b/source/extensions/clusters/eds/eds.h index 18cfdb39c913..88f1969b9136 100644 --- a/source/extensions/clusters/eds/eds.h +++ b/source/extensions/clusters/eds/eds.h @@ -33,13 +33,18 @@ class EdsClusterImpl Envoy::Config::SubscriptionBase, private Config::EdsResourceRemovalCallback { public: - EdsClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, - ClusterFactoryContext& cluster_context); + static absl::StatusOr> + create(const envoy::config::cluster::v3::Cluster& cluster, + ClusterFactoryContext& cluster_context); ~EdsClusterImpl() override; // Upstream::Cluster InitializePhase initializePhase() const override { return initialize_phase_; } +protected: + EdsClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, + ClusterFactoryContext& cluster_context, absl::Status& creation_status); + private: // Config::SubscriptionCallbacks absl::Status onConfigUpdate(const std::vector& resources, diff --git a/source/extensions/clusters/logical_dns/logical_dns_cluster.cc b/source/extensions/clusters/logical_dns/logical_dns_cluster.cc index bd50fe9cc380..927a49804c1f 100644 --- a/source/extensions/clusters/logical_dns/logical_dns_cluster.cc +++ b/source/extensions/clusters/logical_dns/logical_dns_cluster.cc @@ -46,8 +46,9 @@ convertPriority(const envoy::config::endpoint::v3::ClusterLoadAssignment& load_a LogicalDnsCluster::LogicalDnsCluster(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& context, - Network::DnsResolverSharedPtr dns_resolver) - : ClusterImplBase(cluster, context), dns_resolver_(dns_resolver), + Network::DnsResolverSharedPtr dns_resolver, + absl::Status& creation_status) + : ClusterImplBase(cluster, context, creation_status), dns_resolver_(dns_resolver), dns_refresh_rate_ms_( std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(cluster, dns_refresh_rate, 5000))), respect_dns_ttl_(cluster.respect_dns_ttl()), @@ -187,9 +188,13 @@ LogicalDnsClusterFactory::createClusterImpl(const envoy::config::cluster::v3::Cl "LOGICAL_DNS clusters must NOT have a custom resolver name set"); } - return std::make_pair(std::shared_ptr(new LogicalDnsCluster( - cluster, context, std::move(dns_resolver_or_error.value()))), - nullptr); + absl::Status creation_status = absl::OkStatus(); + auto ret = std::make_pair( + std::shared_ptr(new LogicalDnsCluster( + cluster, context, std::move(dns_resolver_or_error.value()), creation_status)), + nullptr); + RETURN_IF_NOT_OK(creation_status); + return ret; } /** diff --git a/source/extensions/clusters/logical_dns/logical_dns_cluster.h b/source/extensions/clusters/logical_dns/logical_dns_cluster.h index c853e0e742d4..b94d551b240b 100644 --- a/source/extensions/clusters/logical_dns/logical_dns_cluster.h +++ b/source/extensions/clusters/logical_dns/logical_dns_cluster.h @@ -41,13 +41,15 @@ class LogicalDnsCluster : public ClusterImplBase { // Upstream::Cluster InitializePhase initializePhase() const override { return InitializePhase::Primary; } +protected: + LogicalDnsCluster(const envoy::config::cluster::v3::Cluster& cluster, + ClusterFactoryContext& context, Network::DnsResolverSharedPtr dns_resolver, + absl::Status& creation_status); + private: friend class LogicalDnsClusterFactory; friend class LogicalDnsClusterTest; - LogicalDnsCluster(const envoy::config::cluster::v3::Cluster& cluster, - ClusterFactoryContext& context, Network::DnsResolverSharedPtr dns_resolver); - const envoy::config::endpoint::v3::LocalityLbEndpoints& localityLbEndpoint() const { // This is checked in the constructor, i.e. at config load time. ASSERT(load_assignment_.endpoints().size() == 1); diff --git a/source/extensions/clusters/original_dst/original_dst_cluster.cc b/source/extensions/clusters/original_dst/original_dst_cluster.cc index 1cd2dbe1a014..ebc28716370f 100644 --- a/source/extensions/clusters/original_dst/original_dst_cluster.cc +++ b/source/extensions/clusters/original_dst/original_dst_cluster.cc @@ -189,8 +189,9 @@ OriginalDstCluster::LoadBalancer::metadataOverrideHost(LoadBalancerContext* cont } OriginalDstCluster::OriginalDstCluster(const envoy::config::cluster::v3::Cluster& config, - ClusterFactoryContext& context) - : ClusterImplBase(config, context), + ClusterFactoryContext& context, + absl::Status& creation_status) + : ClusterImplBase(config, context, creation_status), dispatcher_(context.serverFactoryContext().mainThreadDispatcher()), cleanup_interval_ms_( std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(config, cleanup_interval, 5000))), @@ -343,7 +344,10 @@ OriginalDstClusterFactory::createClusterImpl(const envoy::config::cluster::v3::C // TODO(mattklein123): The original DST load balancer type should be deprecated and instead // the cluster should directly supply the load balancer. This will remove // a special case and allow this cluster to be compiled out as an extension. - auto new_cluster = std::shared_ptr(new OriginalDstCluster(cluster, context)); + absl::Status creation_status = absl::OkStatus(); + auto new_cluster = std::shared_ptr( + new OriginalDstCluster(cluster, context, creation_status)); + RETURN_IF_NOT_OK(creation_status); auto lb = std::make_unique( std::make_shared(new_cluster)); return std::make_pair(new_cluster, std::move(lb)); diff --git a/source/extensions/clusters/original_dst/original_dst_cluster.h b/source/extensions/clusters/original_dst/original_dst_cluster.h index 1a73d779ac5b..8afc58d8b2c6 100644 --- a/source/extensions/clusters/original_dst/original_dst_cluster.h +++ b/source/extensions/clusters/original_dst/original_dst_cluster.h @@ -127,11 +127,13 @@ class OriginalDstCluster : public ClusterImplBase { const absl::optional& metadataKey() { return metadata_key_; } const absl::optional portOverride() { return port_override_; } +protected: + OriginalDstCluster(const envoy::config::cluster::v3::Cluster& config, + ClusterFactoryContext& context, absl::Status& creation_status); + private: friend class OriginalDstClusterFactory; friend class OriginalDstClusterTest; - OriginalDstCluster(const envoy::config::cluster::v3::Cluster& config, - ClusterFactoryContext& context); struct LoadBalancerFactory : public Upstream::LoadBalancerFactory { LoadBalancerFactory(const OriginalDstClusterHandleSharedPtr& cluster) : cluster_(cluster) {} @@ -192,11 +194,12 @@ class OriginalDstClusterFactory : public ClusterFactoryImplBase { public: OriginalDstClusterFactory() : ClusterFactoryImplBase("envoy.cluster.original_dst") {} -private: - friend class OriginalDstClusterTest; absl::StatusOr> createClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& context) override; + +private: + friend class OriginalDstClusterTest; }; DECLARE_FACTORY(OriginalDstClusterFactory); diff --git a/source/extensions/clusters/redis/redis_cluster.cc b/source/extensions/clusters/redis/redis_cluster.cc index 696703e500ef..d667efea6d9b 100644 --- a/source/extensions/clusters/redis/redis_cluster.cc +++ b/source/extensions/clusters/redis/redis_cluster.cc @@ -14,13 +14,27 @@ namespace Extensions { namespace Clusters { namespace Redis { +absl::StatusOr> RedisCluster::create( + const envoy::config::cluster::v3::Cluster& cluster, + const envoy::extensions::clusters::redis::v3::RedisClusterConfig& redis_cluster, + Upstream::ClusterFactoryContext& context, + NetworkFilters::Common::Redis::Client::ClientFactory& client_factory, + Network::DnsResolverSharedPtr dns_resolver, ClusterSlotUpdateCallBackSharedPtr factory) { + absl::Status creation_status = absl::OkStatus(); + std::unique_ptr ret = absl::WrapUnique(new RedisCluster( + cluster, redis_cluster, context, client_factory, dns_resolver, factory, creation_status)); + RETURN_IF_NOT_OK(creation_status); + return ret; +} + RedisCluster::RedisCluster( const envoy::config::cluster::v3::Cluster& cluster, const envoy::extensions::clusters::redis::v3::RedisClusterConfig& redis_cluster, Upstream::ClusterFactoryContext& context, NetworkFilters::Common::Redis::Client::ClientFactory& redis_client_factory, - Network::DnsResolverSharedPtr dns_resolver, ClusterSlotUpdateCallBackSharedPtr lb_factory) - : Upstream::BaseDynamicClusterImpl(cluster, context), + Network::DnsResolverSharedPtr dns_resolver, ClusterSlotUpdateCallBackSharedPtr lb_factory, + absl::Status& creation_status) + : Upstream::BaseDynamicClusterImpl(cluster, context, creation_status), cluster_manager_(context.clusterManager()), cluster_refresh_rate_(std::chrono::milliseconds( PROTOBUF_GET_MS_OR_DEFAULT(redis_cluster, cluster_refresh_rate, 5000))), @@ -585,19 +599,24 @@ RedisClusterFactory::createClusterWithConfig( THROW_OR_RETURN_VALUE(selectDnsResolver(cluster, context), Network::DnsResolverSharedPtr); // TODO(hyang): This is needed to migrate existing cluster, disallow using other lb_policy // in the future + absl::Status creation_status = absl::OkStatus(); if (cluster.lb_policy() != envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED) { - return std::make_pair(std::make_shared( - cluster, proto_config, context, - NetworkFilters::Common::Redis::Client::ClientFactoryImpl::instance_, - resolver, nullptr), - nullptr); + auto ret = + std::make_pair(std::shared_ptr(new RedisCluster( + cluster, proto_config, context, + NetworkFilters::Common::Redis::Client::ClientFactoryImpl::instance_, + resolver, nullptr, creation_status)), + nullptr); + RETURN_IF_NOT_OK(creation_status); + return ret; } auto lb_factory = std::make_shared( context.serverFactoryContext().api().randomGenerator()); - return std::make_pair(std::make_shared( - cluster, proto_config, context, - NetworkFilters::Common::Redis::Client::ClientFactoryImpl::instance_, - resolver, lb_factory), + absl::StatusOr> cluster_or_error = RedisCluster::create( + cluster, proto_config, context, + NetworkFilters::Common::Redis::Client::ClientFactoryImpl::instance_, resolver, lb_factory); + RETURN_IF_NOT_OK(cluster_or_error.status()); + return std::make_pair(std::shared_ptr(std::move(*cluster_or_error)), std::make_unique(lb_factory)); } diff --git a/source/extensions/clusters/redis/redis_cluster.h b/source/extensions/clusters/redis/redis_cluster.h index 18904d22c0f0..c23aed876c37 100644 --- a/source/extensions/clusters/redis/redis_cluster.h +++ b/source/extensions/clusters/redis/redis_cluster.h @@ -90,12 +90,12 @@ namespace Redis { class RedisCluster : public Upstream::BaseDynamicClusterImpl { public: - RedisCluster(const envoy::config::cluster::v3::Cluster& cluster, - const envoy::extensions::clusters::redis::v3::RedisClusterConfig& redis_cluster, - Upstream::ClusterFactoryContext& context, - NetworkFilters::Common::Redis::Client::ClientFactory& client_factory, - Network::DnsResolverSharedPtr dns_resolver, - ClusterSlotUpdateCallBackSharedPtr factory); + static absl::StatusOr> + create(const envoy::config::cluster::v3::Cluster& cluster, + const envoy::extensions::clusters::redis::v3::RedisClusterConfig& redis_cluster, + Upstream::ClusterFactoryContext& context, + NetworkFilters::Common::Redis::Client::ClientFactory& client_factory, + Network::DnsResolverSharedPtr dns_resolver, ClusterSlotUpdateCallBackSharedPtr factory); struct ClusterSlotsRequest : public Extensions::NetworkFilters::Common::Redis::RespValue { public: @@ -116,8 +116,20 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl { TimeSource& timeSource() const { return time_source_; } +protected: + RedisCluster(const envoy::config::cluster::v3::Cluster& cluster, + const envoy::extensions::clusters::redis::v3::RedisClusterConfig& redis_cluster, + Upstream::ClusterFactoryContext& context, + NetworkFilters::Common::Redis::Client::ClientFactory& client_factory, + Network::DnsResolverSharedPtr dns_resolver, + ClusterSlotUpdateCallBackSharedPtr factory, absl::Status& creation_status); + private: + friend class RedisClusterFactory; + friend class RedisClusterTest; + friend class RedisClusterTest; + friend class RedisClsuterFactory; void startPreInit() override; diff --git a/source/extensions/clusters/static/static_cluster.cc b/source/extensions/clusters/static/static_cluster.cc index 704926c9ea17..d55571f62353 100644 --- a/source/extensions/clusters/static/static_cluster.cc +++ b/source/extensions/clusters/static/static_cluster.cc @@ -8,10 +8,11 @@ namespace Envoy { namespace Upstream { StaticClusterImpl::StaticClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, - ClusterFactoryContext& context) - : ClusterImplBase(cluster, context), - priority_state_manager_(new PriorityStateManager( - *this, context.serverFactoryContext().localInfo(), nullptr, random_)) { + ClusterFactoryContext& context, absl::Status& creation_status) + : ClusterImplBase(cluster, context, creation_status) { + SET_AND_RETURN_IF_NOT_OK(creation_status, creation_status); + priority_state_manager_.reset(new PriorityStateManager( + *this, context.serverFactoryContext().localInfo(), nullptr, random_)); const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment = cluster.load_assignment(); overprovisioning_factor_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT( @@ -68,8 +69,12 @@ StaticClusterFactory::createClusterImpl(const envoy::config::cluster::v3::Cluste cluster.name())); } } - return std::make_pair(std::shared_ptr(new StaticClusterImpl(cluster, context)), - nullptr); + absl::Status creation_status = absl::OkStatus(); + auto ret = std::make_pair( + std::shared_ptr(new StaticClusterImpl(cluster, context, creation_status)), + nullptr); + RETURN_IF_NOT_OK(creation_status); + return ret; } /** diff --git a/source/extensions/clusters/static/static_cluster.h b/source/extensions/clusters/static/static_cluster.h index c0486ec0216b..345e12670fc3 100644 --- a/source/extensions/clusters/static/static_cluster.h +++ b/source/extensions/clusters/static/static_cluster.h @@ -19,13 +19,14 @@ class StaticClusterImpl : public ClusterImplBase { // Upstream::Cluster InitializePhase initializePhase() const override { return InitializePhase::Primary; } +protected: + StaticClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, + ClusterFactoryContext& context, absl::Status& creation_status); + private: friend class StaticClusterFactory; friend class UpstreamImplTestBase; - StaticClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, - ClusterFactoryContext& context); - // ClusterImplBase void startPreInit() override; diff --git a/source/extensions/clusters/strict_dns/strict_dns_cluster.cc b/source/extensions/clusters/strict_dns/strict_dns_cluster.cc index 8b67115c3cc4..a052fd9cb3df 100644 --- a/source/extensions/clusters/strict_dns/strict_dns_cluster.cc +++ b/source/extensions/clusters/strict_dns/strict_dns_cluster.cc @@ -8,10 +8,24 @@ namespace Envoy { namespace Upstream { +absl::StatusOr> +StrictDnsClusterImpl::create(const envoy::config::cluster::v3::Cluster& cluster, + ClusterFactoryContext& context, + Network::DnsResolverSharedPtr dns_resolver) { + absl::Status creation_status = absl::OkStatus(); + auto ret = std::unique_ptr( + new StrictDnsClusterImpl(cluster, context, dns_resolver, creation_status)); + + RETURN_IF_NOT_OK(creation_status); + return ret; +} + StrictDnsClusterImpl::StrictDnsClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& context, - Network::DnsResolverSharedPtr dns_resolver) - : BaseDynamicClusterImpl(cluster, context), load_assignment_(cluster.load_assignment()), + Network::DnsResolverSharedPtr dns_resolver, + absl::Status& creation_status) + : BaseDynamicClusterImpl(cluster, context, creation_status), + load_assignment_(cluster.load_assignment()), local_info_(context.serverFactoryContext().localInfo()), dns_resolver_(dns_resolver), dns_refresh_rate_ms_( std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(cluster, dns_refresh_rate, 5000))), @@ -199,10 +213,12 @@ absl::StatusOr> StrictDnsClusterFactory::createClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& context) { auto dns_resolver_or_error = selectDnsResolver(cluster, context); - THROW_IF_NOT_OK(dns_resolver_or_error.status()); + RETURN_IF_NOT_OK(dns_resolver_or_error.status()); - return std::make_pair(std::make_shared( - cluster, context, std::move(dns_resolver_or_error.value())), + auto cluster_or_error = + StrictDnsClusterImpl::create(cluster, context, std::move(dns_resolver_or_error.value())); + RETURN_IF_NOT_OK(cluster_or_error.status()); + return std::make_pair(std::shared_ptr(std::move(*cluster_or_error)), nullptr); } diff --git a/source/extensions/clusters/strict_dns/strict_dns_cluster.h b/source/extensions/clusters/strict_dns/strict_dns_cluster.h index 432dd24dae42..6d8cd9f3923d 100644 --- a/source/extensions/clusters/strict_dns/strict_dns_cluster.h +++ b/source/extensions/clusters/strict_dns/strict_dns_cluster.h @@ -15,11 +15,16 @@ namespace Upstream { */ class StrictDnsClusterImpl : public BaseDynamicClusterImpl { public: - StrictDnsClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, - ClusterFactoryContext& context, Network::DnsResolverSharedPtr dns_resolver); - // Upstream::Cluster InitializePhase initializePhase() const override { return InitializePhase::Primary; } + static absl::StatusOr> + create(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& context, + Network::DnsResolverSharedPtr dns_resolver); + +protected: + StrictDnsClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, + ClusterFactoryContext& context, Network::DnsResolverSharedPtr dns_resolver, + absl::Status& creation_status); private: struct ResolveTarget { diff --git a/test/common/upstream/cluster_factory_impl_test.cc b/test/common/upstream/cluster_factory_impl_test.cc index 9039a1668b89..5a1f1afd11c1 100644 --- a/test/common/upstream/cluster_factory_impl_test.cc +++ b/test/common/upstream/cluster_factory_impl_test.cc @@ -43,8 +43,10 @@ class TestStaticClusterFactory : public Event::TestUsingSimulatedTime, absl::StatusOr> createClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& context) override { - return std::make_pair( - std::make_shared(cluster, context, 1, "127.0.0.1", 80), nullptr); + absl::Status creation_status = absl::OkStatus(); + return std::make_pair(std::make_shared(cluster, context, 1, "127.0.0.1", + 80, creation_status), + nullptr); } }; diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index f136279edfd0..c4f5373d9469 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -195,12 +195,13 @@ class StrictDnsParamTest : public testing::TestWithParam, server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); if (numerator <= 100) { - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver); - EXPECT_EQ(drop_ratio, cluster.dropOverload().value()); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver); + EXPECT_EQ(drop_ratio, cluster->dropOverload().value()); } else { - EXPECT_THROW_WITH_MESSAGE( - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver), - EnvoyException, + EXPECT_EQ( + StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver) + .status() + .message(), fmt::format("load_balancing_policy.drop_overload_limit runtime key config {} is invalid. " "The valid range is 0~100", numerator)); @@ -243,11 +244,11 @@ TEST_P(StrictDnsParamTest, ImmediateResolve) { Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver); - cluster.initialize([&]() -> void { initialized.ready(); }); - EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); - EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + cluster->initialize([&]() -> void { initialized.ready(); }); + EXPECT_EQ(2UL, cluster->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(2UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); } TEST_P(StrictDnsParamTest, DropOverLoadConfigTestBasicMillion) { @@ -272,8 +273,8 @@ TEST_P(StrictDnsParamTest, DropOverLoadConfigTestBasicMillion) { Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver); - EXPECT_EQ(0.000035f, cluster.dropOverload().value()); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver); + EXPECT_EQ(0.000035f, cluster->dropOverload().value()); } TEST_P(StrictDnsParamTest, DropOverLoadConfigTestBasicTenThousand) { @@ -298,8 +299,8 @@ TEST_P(StrictDnsParamTest, DropOverLoadConfigTestBasicTenThousand) { Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver); - EXPECT_EQ(0.1f, cluster.dropOverload().value()); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver); + EXPECT_EQ(0.1f, cluster->dropOverload().value()); } TEST_P(StrictDnsParamTest, DropOverLoadConfigTestBadDenominator) { @@ -325,9 +326,10 @@ TEST_P(StrictDnsParamTest, DropOverLoadConfigTestBadDenominator) { Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - EXPECT_THROW_WITH_MESSAGE( - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver), EnvoyException, - "Cluster drop_overloads config denominator setting is invalid : 4. Valid range 0~2."); + EXPECT_EQ(StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver) + .status() + .message(), + "Cluster drop_overloads config denominator setting is invalid : 4. Valid range 0~2."); } TEST_P(StrictDnsParamTest, DropOverLoadConfigTestBadNumerator) { @@ -353,8 +355,10 @@ TEST_P(StrictDnsParamTest, DropOverLoadConfigTestBadNumerator) { Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - EXPECT_THROW_WITH_MESSAGE( - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver), EnvoyException, + EXPECT_EQ( + StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver) + .status() + .message(), "Cluster drop_overloads config is invalid. drop_ratio=2(Numerator 200 / Denominator 100). " "The valid range is 0~1."); } @@ -384,9 +388,10 @@ TEST_P(StrictDnsParamTest, DropOverLoadConfigTestMultipleCategory) { Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - EXPECT_THROW_WITH_MESSAGE( - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver), EnvoyException, - "Cluster drop_overloads config has 2 categories. Envoy only support one."); + EXPECT_EQ(StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver) + .status() + .message(), + "Cluster drop_overloads config has 2 categories. Envoy only support one."); } // Drop overload runtime key configuration test @@ -430,11 +435,11 @@ TEST_F(StrictDnsClusterImplTest, ZeroHostsIsInializedImmediately) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); EXPECT_CALL(initialized, ready()); - cluster.initialize([&]() -> void { initialized.ready(); }); - EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); - EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + cluster->initialize([&]() -> void { initialized.ready(); }); + EXPECT_EQ(0UL, cluster->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(0UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); } // Resolve zero hosts, while using health checking. @@ -463,19 +468,19 @@ TEST_F(StrictDnsClusterImplTest, ZeroHostsHealthChecker) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); std::shared_ptr health_checker(new MockHealthChecker()); EXPECT_CALL(*health_checker, start()); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); - cluster.setHealthChecker(health_checker); - cluster.initialize([&]() -> void { initialized.ready(); }); + cluster->setHealthChecker(health_checker); + cluster->initialize([&]() -> void { initialized.ready(); }); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); EXPECT_CALL(initialized, ready()); EXPECT_CALL(*resolver.timer_, enableTimer(_, _)); resolver.dns_callback_(Network::DnsResolver::ResolutionStatus::Success, "", {}); - EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); - EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(0UL, cluster->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(0UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); } TEST_F(StrictDnsClusterImplTest, DontWaitForDNSOnInit) { @@ -507,16 +512,16 @@ TEST_F(StrictDnsClusterImplTest, DontWaitForDNSOnInit) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); ReadyWatcher initialized; // Initialized without completing DNS resolution. EXPECT_CALL(initialized, ready()); - cluster.initialize([&]() -> void { initialized.ready(); }); + cluster->initialize([&]() -> void { initialized.ready(); }); ReadyWatcher membership_updated; - auto priority_update_cb = cluster.prioritySet().addPriorityUpdateCb( + auto priority_update_cb = cluster->prioritySet().addPriorityUpdateCb( [&](uint32_t, const HostVector&, const HostVector&) { membership_updated.ready(); return absl::OkStatus(); @@ -588,48 +593,49 @@ TEST_F(StrictDnsClusterImplTest, Basic) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.default.max_connections", 43)) .Times(AnyNumber()); - EXPECT_EQ(43U, cluster.info()->resourceManager(ResourcePriority::Default).connections().max()); + EXPECT_EQ(43U, cluster->info()->resourceManager(ResourcePriority::Default).connections().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.default.max_pending_requests", 57)); EXPECT_EQ(57U, - cluster.info()->resourceManager(ResourcePriority::Default).pendingRequests().max()); + cluster->info()->resourceManager(ResourcePriority::Default).pendingRequests().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.default.max_requests", 50)); - EXPECT_EQ(50U, cluster.info()->resourceManager(ResourcePriority::Default).requests().max()); + EXPECT_EQ(50U, cluster->info()->resourceManager(ResourcePriority::Default).requests().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.default.max_retries", 10)); - EXPECT_EQ(10U, cluster.info()->resourceManager(ResourcePriority::Default).retries().max()); + EXPECT_EQ(10U, cluster->info()->resourceManager(ResourcePriority::Default).retries().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.high.max_connections", 1)); - EXPECT_EQ(1U, cluster.info()->resourceManager(ResourcePriority::High).connections().max()); + EXPECT_EQ(1U, cluster->info()->resourceManager(ResourcePriority::High).connections().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.high.max_pending_requests", 2)); - EXPECT_EQ(2U, cluster.info()->resourceManager(ResourcePriority::High).pendingRequests().max()); + EXPECT_EQ(2U, cluster->info()->resourceManager(ResourcePriority::High).pendingRequests().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.high.max_requests", 3)); - EXPECT_EQ(3U, cluster.info()->resourceManager(ResourcePriority::High).requests().max()); + EXPECT_EQ(3U, cluster->info()->resourceManager(ResourcePriority::High).requests().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.high.max_retries", 4)); - EXPECT_EQ(4U, cluster.info()->resourceManager(ResourcePriority::High).retries().max()); - EXPECT_EQ(3U, cluster.info()->maxRequestsPerConnection()); - EXPECT_EQ(0U, cluster.info()->http2Options().hpack_table_size().value()); + EXPECT_EQ(4U, cluster->info()->resourceManager(ResourcePriority::High).retries().max()); + EXPECT_EQ(3U, cluster->info()->maxRequestsPerConnection()); + EXPECT_EQ(0U, cluster->info()->http2Options().hpack_table_size().value()); EXPECT_EQ(Http::Http1Settings::HeaderKeyFormat::ProperCase, - cluster.info()->http1Settings().header_key_format_); - EXPECT_EQ(1U, cluster.info()->resourceManager(ResourcePriority::Default).maxConnectionsPerHost()); - EXPECT_EQ(990U, cluster.info()->resourceManager(ResourcePriority::High).maxConnectionsPerHost()); + cluster->info()->http1Settings().header_key_format_); + EXPECT_EQ(1U, + cluster->info()->resourceManager(ResourcePriority::Default).maxConnectionsPerHost()); + EXPECT_EQ(990U, cluster->info()->resourceManager(ResourcePriority::High).maxConnectionsPerHost()); - cluster.info()->trafficStats()->upstream_rq_total_.inc(); + cluster->info()->trafficStats()->upstream_rq_total_.inc(); EXPECT_EQ(1UL, stats_.counter("cluster.name.upstream_rq_total").value()); EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.maintenance_mode.name", 0)); - EXPECT_FALSE(cluster.info()->maintenanceMode()); + EXPECT_FALSE(cluster->info()->maintenanceMode()); ReadyWatcher membership_updated; - auto priority_update_cb = cluster.prioritySet().addPriorityUpdateCb( + auto priority_update_cb = cluster->prioritySet().addPriorityUpdateCb( [&](uint32_t, const HostVector&, const HostVector&) { membership_updated.ready(); return absl::OkStatus(); }); - cluster.initialize([] {}); + cluster->initialize([] {}); resolver1.expectResolve(*dns_resolver_); EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -638,9 +644,9 @@ TEST_F(StrictDnsClusterImplTest, Basic) { TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); - EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); - EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); + EXPECT_EQ("localhost1", cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_EQ("localhost1", cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); @@ -649,7 +655,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); @@ -658,7 +664,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); resolver1.timer_->invokeCallback(); EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -667,7 +673,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { TestUtility::makeDnsResponse({"127.0.0.3"})); EXPECT_THAT( std::list({"127.0.0.3:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); // Make sure we de-dup the same address. EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -676,17 +682,17 @@ TEST_F(StrictDnsClusterImplTest, Basic) { TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.3:11001", "10.0.0.1:11002"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); - EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); - EXPECT_EQ("foo", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); + EXPECT_EQ("localhost1", cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_EQ("foo", cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); - EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); - EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); - EXPECT_EQ(1UL, - cluster.prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); + EXPECT_EQ(2UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster->prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); + EXPECT_EQ( + 1UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); - for (const HostSharedPtr& host : cluster.prioritySet().hostSetsPerPriority()[0]->hosts()) { - EXPECT_EQ(cluster.info().get(), &host->cluster()); + for (const HostSharedPtr& host : cluster->prioritySet().hostSetsPerPriority()[0]->hosts()) { + EXPECT_EQ(cluster->info().get(), &host->cluster()); } // Empty response. With successful but empty response the host list deletes the address. @@ -698,7 +704,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { TestUtility::makeDnsResponse({})); EXPECT_THAT( std::list({"10.0.0.1:11002"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); // Empty response. With failing but empty response the host list does not delete the address. ON_CALL(random_, random()).WillByDefault(Return(8000)); @@ -709,7 +715,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { TestUtility::makeDnsResponse({})); EXPECT_THAT( std::list({"10.0.0.1:11002"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); // Make sure we cancel. resolver1.expectResolve(*dns_resolver_); @@ -723,7 +729,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { cancel(Network::ActiveDnsQuery::CancelReason::QueryAbandoned)); // Test per host connection limits: as it's set to 1, the host can create connections initially. - auto& host = cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]; + auto& host = cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[0]; EXPECT_TRUE(host->canCreateConnection(ResourcePriority::Default)); // If one connection exists to that host, canCreateConnection will fail. host->stats().cx_active_.inc(); @@ -756,13 +762,13 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalActiveHealthSkipped) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); std::shared_ptr health_checker(new MockHealthChecker()); EXPECT_CALL(*health_checker, start()); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); - cluster.setHealthChecker(health_checker); - cluster.initialize([&]() -> void {}); + cluster->setHealthChecker(health_checker); + cluster->initialize([&]() -> void {}); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); EXPECT_CALL(*resolver.timer_, enableTimer(_, _)).Times(2); @@ -773,7 +779,7 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalActiveHealthSkipped) { // clear the flag to simulate that these endpoints have been successfully health // checked. { - const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + const auto& hosts = cluster->prioritySet().hostSetsPerPriority()[0]->hosts(); EXPECT_EQ(2UL, hosts.size()); for (const auto& host : hosts) { @@ -787,7 +793,7 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalActiveHealthSkipped) { resolver.dns_callback_(Network::DnsResolver::ResolutionStatus::Success, "", TestUtility::makeDnsResponse({"127.0.0.1"})); - const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + const auto& hosts = cluster->prioritySet().hostSetsPerPriority()[0]->hosts(); EXPECT_EQ(1UL, hosts.size()); } @@ -816,14 +822,14 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalAfterHcFail) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); std::shared_ptr health_checker(new MockHealthChecker()); EXPECT_CALL(*health_checker, start()); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); - cluster.setHealthChecker(health_checker); + cluster->setHealthChecker(health_checker); ReadyWatcher initialized; - cluster.initialize([&initialized]() { initialized.ready(); }); + cluster->initialize([&initialized]() { initialized.ready(); }); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); EXPECT_CALL(*resolver.timer_, enableTimer(_, _)).Times(2); @@ -834,7 +840,7 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalAfterHcFail) { // clear the flag to simulate that these endpoints have been successfully health // checked. { - const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + const auto& hosts = cluster->prioritySet().hostSetsPerPriority()[0]->hosts(); EXPECT_EQ(2UL, hosts.size()); for (size_t i = 0; i < 2; ++i) { @@ -853,7 +859,7 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalAfterHcFail) { TestUtility::makeDnsResponse({"127.0.0.1"})); { - const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + const auto& hosts = cluster->prioritySet().hostSetsPerPriority()[0]->hosts(); EXPECT_EQ(2UL, hosts.size()); EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::PENDING_DYNAMIC_REMOVAL)); EXPECT_TRUE(hosts[1]->healthFlagGet(Host::HealthFlag::PENDING_DYNAMIC_REMOVAL)); @@ -865,7 +871,7 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalAfterHcFail) { // Unlike EDS we will not remove if HC is failing but will wait until the next polling interval. // This may change in the future. { - const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + const auto& hosts = cluster->prioritySet().hostSetsPerPriority()[0]->hosts(); EXPECT_EQ(2UL, hosts.size()); } } @@ -895,14 +901,14 @@ TEST_F(StrictDnsClusterImplTest, HostUpdateWithDisabledACEndpoint) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); std::shared_ptr health_checker(new MockHealthChecker()); EXPECT_CALL(*health_checker, start()); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); - cluster.setHealthChecker(health_checker); + cluster->setHealthChecker(health_checker); ReadyWatcher initialized; - cluster.initialize([&initialized]() { initialized.ready(); }); + cluster->initialize([&initialized]() { initialized.ready(); }); EXPECT_CALL(initialized, ready()); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); @@ -911,14 +917,14 @@ TEST_F(StrictDnsClusterImplTest, HostUpdateWithDisabledACEndpoint) { TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); { - const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + const auto& hosts = cluster->prioritySet().hostSetsPerPriority()[0]->hosts(); EXPECT_EQ(2UL, hosts.size()); EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); EXPECT_FALSE(hosts[1]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); - EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); - EXPECT_EQ(2UL, cluster.info()->endpointStats().membership_healthy_.value()); - EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); + EXPECT_EQ(2UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(2UL, cluster->info()->endpointStats().membership_healthy_.value()); + EXPECT_EQ(0UL, cluster->info()->endpointStats().membership_degraded_.value()); } // Re-resolve the DNS name with only one record, we should have 1 host. @@ -926,13 +932,13 @@ TEST_F(StrictDnsClusterImplTest, HostUpdateWithDisabledACEndpoint) { TestUtility::makeDnsResponse({"127.0.0.1"})); { - const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + const auto& hosts = cluster->prioritySet().hostSetsPerPriority()[0]->hosts(); EXPECT_EQ(1UL, hosts.size()); EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::PENDING_DYNAMIC_REMOVAL)); EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); - EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); - EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_healthy_.value()); - EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); + EXPECT_EQ(1UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster->info()->endpointStats().membership_healthy_.value()); + EXPECT_EQ(0UL, cluster->info()->endpointStats().membership_degraded_.value()); } } @@ -1009,43 +1015,43 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.default.max_connections", 43)); - EXPECT_EQ(43U, cluster.info()->resourceManager(ResourcePriority::Default).connections().max()); + EXPECT_EQ(43U, cluster->info()->resourceManager(ResourcePriority::Default).connections().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.default.max_pending_requests", 57)); EXPECT_EQ(57U, - cluster.info()->resourceManager(ResourcePriority::Default).pendingRequests().max()); + cluster->info()->resourceManager(ResourcePriority::Default).pendingRequests().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.default.max_requests", 50)); - EXPECT_EQ(50U, cluster.info()->resourceManager(ResourcePriority::Default).requests().max()); + EXPECT_EQ(50U, cluster->info()->resourceManager(ResourcePriority::Default).requests().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.default.max_retries", 10)); - EXPECT_EQ(10U, cluster.info()->resourceManager(ResourcePriority::Default).retries().max()); + EXPECT_EQ(10U, cluster->info()->resourceManager(ResourcePriority::Default).retries().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.high.max_connections", 1)); - EXPECT_EQ(1U, cluster.info()->resourceManager(ResourcePriority::High).connections().max()); + EXPECT_EQ(1U, cluster->info()->resourceManager(ResourcePriority::High).connections().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.high.max_pending_requests", 2)); - EXPECT_EQ(2U, cluster.info()->resourceManager(ResourcePriority::High).pendingRequests().max()); + EXPECT_EQ(2U, cluster->info()->resourceManager(ResourcePriority::High).pendingRequests().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.high.max_requests", 3)); - EXPECT_EQ(3U, cluster.info()->resourceManager(ResourcePriority::High).requests().max()); + EXPECT_EQ(3U, cluster->info()->resourceManager(ResourcePriority::High).requests().max()); EXPECT_CALL(runtime_.snapshot_, getInteger("circuit_breakers.name.high.max_retries", 4)); - EXPECT_EQ(4U, cluster.info()->resourceManager(ResourcePriority::High).retries().max()); - EXPECT_EQ(3U, cluster.info()->maxRequestsPerConnection()); - EXPECT_EQ(0U, cluster.info()->http2Options().hpack_table_size().value()); + EXPECT_EQ(4U, cluster->info()->resourceManager(ResourcePriority::High).retries().max()); + EXPECT_EQ(3U, cluster->info()->maxRequestsPerConnection()); + EXPECT_EQ(0U, cluster->info()->http2Options().hpack_table_size().value()); - cluster.info()->trafficStats()->upstream_rq_total_.inc(); + cluster->info()->trafficStats()->upstream_rq_total_.inc(); EXPECT_EQ(1UL, stats_.counter("cluster.name.upstream_rq_total").value()); EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.maintenance_mode.name", 0)); - EXPECT_FALSE(cluster.info()->maintenanceMode()); + EXPECT_FALSE(cluster->info()->maintenanceMode()); ReadyWatcher membership_updated; - auto priority_update_cb = cluster.prioritySet().addPriorityUpdateCb( + auto priority_update_cb = cluster->prioritySet().addPriorityUpdateCb( [&](uint32_t, const HostVector&, const HostVector&) { membership_updated.ready(); return absl::OkStatus(); }); - cluster.initialize([] {}); + cluster->initialize([] {}); resolver1.expectResolve(*dns_resolver_); EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -1054,15 +1060,15 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); - EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); - EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); - EXPECT_TRUE(cluster.prioritySet().hostSetsPerPriority()[0]->weightedPriorityHealth()); - EXPECT_EQ(100, cluster.prioritySet().hostSetsPerPriority()[0]->overprovisioningFactor()); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); + EXPECT_EQ("localhost1", cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_EQ("localhost1", cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); + EXPECT_TRUE(cluster->prioritySet().hostSetsPerPriority()[0]->weightedPriorityHealth()); + EXPECT_EQ(100, cluster->prioritySet().hostSetsPerPriority()[0]->overprovisioningFactor()); EXPECT_EQ(Host::Health::Degraded, - cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->coarseHealth()); + cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->coarseHealth()); EXPECT_EQ(Host::Health::Degraded, - cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->coarseHealth()); + cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[1]->coarseHealth()); // This is the first time we received an update for localhost1, we expect to rebuild. EXPECT_EQ(0UL, stats_.counter("cluster.name.update_no_rebuild").value()); @@ -1074,9 +1080,9 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); - EXPECT_TRUE(cluster.prioritySet().hostSetsPerPriority()[0]->weightedPriorityHealth()); - EXPECT_EQ(100, cluster.prioritySet().hostSetsPerPriority()[0]->overprovisioningFactor()); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); + EXPECT_TRUE(cluster->prioritySet().hostSetsPerPriority()[0]->weightedPriorityHealth()); + EXPECT_EQ(100, cluster->prioritySet().hostSetsPerPriority()[0]->overprovisioningFactor()); // Since no change for localhost1, we expect no rebuild. EXPECT_EQ(1UL, stats_.counter("cluster.name.update_no_rebuild").value()); @@ -1088,9 +1094,9 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); - EXPECT_TRUE(cluster.prioritySet().hostSetsPerPriority()[0]->weightedPriorityHealth()); - EXPECT_EQ(100, cluster.prioritySet().hostSetsPerPriority()[0]->overprovisioningFactor()); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); + EXPECT_TRUE(cluster->prioritySet().hostSetsPerPriority()[0]->weightedPriorityHealth()); + EXPECT_EQ(100, cluster->prioritySet().hostSetsPerPriority()[0]->overprovisioningFactor()); // Since no change for localhost1, we expect no rebuild. EXPECT_EQ(2UL, stats_.counter("cluster.name.update_no_rebuild").value()); @@ -1100,7 +1106,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { resolver2.dns_callback_(Network::DnsResolver::ResolutionStatus::Success, "", TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); - // We received a new set of hosts for localhost2. Should rebuild the cluster. + // We received a new set of hosts for localhost2. Should rebuild the cluster-> EXPECT_EQ(2UL, stats_.counter("cluster.name.update_no_rebuild").value()); resolver1.expectResolve(*dns_resolver_); @@ -1119,7 +1125,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { TestUtility::makeDnsResponse({"127.0.0.3"})); EXPECT_THAT( std::list({"127.0.0.3:11001", "10.0.0.1:11002"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); // Make sure we de-dup the same address. EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -1127,13 +1133,13 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.3:11001", "10.0.0.1:11002"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); - EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); - EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->degradedHosts().size()); - EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); - EXPECT_EQ(1UL, - cluster.prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); + EXPECT_EQ(1UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster->prioritySet().hostSetsPerPriority()[0]->degradedHosts().size()); + EXPECT_EQ(1UL, cluster->prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); + EXPECT_EQ( + 1UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); // Make sure that we *don't* de-dup between resolve targets. EXPECT_CALL(*resolver3.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -1141,29 +1147,29 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { resolver3.dns_callback_(Network::DnsResolver::ResolutionStatus::Success, "", TestUtility::makeDnsResponse({"10.0.0.1"})); - const auto hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + const auto hosts = cluster->prioritySet().hostSetsPerPriority()[0]->hosts(); EXPECT_THAT(std::list({"127.0.0.3:11001", "10.0.0.1:11002", "10.0.0.1:11002"}), ContainerEq(hostListToAddresses(hosts))); - EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); - EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->degradedHosts().size()); - EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); - EXPECT_EQ(1UL, - cluster.prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); + EXPECT_EQ(2UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster->prioritySet().hostSetsPerPriority()[0]->degradedHosts().size()); + EXPECT_EQ(1UL, cluster->prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); + EXPECT_EQ( + 1UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); // Ensure that all host objects in the host list are unique. for (const auto& host : hosts) { EXPECT_EQ(1, std::count(hosts.begin(), hosts.end(), host)); } - for (const HostSharedPtr& host : cluster.prioritySet().hostSetsPerPriority()[0]->hosts()) { - EXPECT_EQ(cluster.info().get(), &host->cluster()); + for (const HostSharedPtr& host : cluster->prioritySet().hostSetsPerPriority()[0]->hosts()) { + EXPECT_EQ(cluster->info().get(), &host->cluster()); } // Remove the duplicated hosts from both resolve targets and ensure that we don't see the same // host multiple times. absl::node_hash_set removed_hosts; - auto priority_update_cb2 = cluster.prioritySet().addPriorityUpdateCb( + auto priority_update_cb2 = cluster->prioritySet().addPriorityUpdateCb( [&](uint32_t, const HostVector&, const HostVector& hosts_removed) { for (const auto& host : hosts_removed) { EXPECT_EQ(removed_hosts.end(), removed_hosts.find(host)); @@ -1253,16 +1259,16 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); ReadyWatcher membership_updated; - auto priority_update_cb = cluster.prioritySet().addPriorityUpdateCb( + auto priority_update_cb = cluster->prioritySet().addPriorityUpdateCb( [&](uint32_t, const HostVector&, const HostVector&) { membership_updated.ready(); return absl::OkStatus(); }); - cluster.initialize([] {}); + cluster->initialize([] {}); resolver1.expectResolve(*dns_resolver_); EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -1271,9 +1277,9 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); - EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); - EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); + EXPECT_EQ("localhost1", cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_EQ("localhost1", cluster->prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); @@ -1282,7 +1288,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); @@ -1291,7 +1297,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); resolver1.timer_->invokeCallback(); EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -1300,7 +1306,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { TestUtility::makeDnsResponse({"127.0.0.3"})); EXPECT_THAT( std::list({"127.0.0.3:11001"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); // Make sure we de-dup the same address. EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -1309,15 +1315,15 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.3:11001", "10.0.0.1:11002"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[0]->hosts()))); - EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); - EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); - EXPECT_EQ(1UL, - cluster.prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); + EXPECT_EQ(2UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster->prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); + EXPECT_EQ( + 1UL, cluster->prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); - for (const HostSharedPtr& host : cluster.prioritySet().hostSetsPerPriority()[0]->hosts()) { - EXPECT_EQ(cluster.info().get(), &host->cluster()); + for (const HostSharedPtr& host : cluster->prioritySet().hostSetsPerPriority()[0]->hosts()) { + EXPECT_EQ(cluster->info().get(), &host->cluster()); } EXPECT_CALL(*resolver3.timer_, enableTimer(std::chrono::milliseconds(4000), _)); @@ -1328,7 +1334,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { // Make sure we have multiple priorities. EXPECT_THAT( std::list({"192.168.1.1:11003", "192.168.1.2:11003"}), - ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[1]->hosts()))); + ContainerEq(hostListToAddresses(cluster->prioritySet().hostSetsPerPriority()[1]->hosts()))); // Make sure we cancel. resolver1.expectResolve(*dns_resolver_); @@ -1372,8 +1378,8 @@ TEST_F(StrictDnsClusterImplTest, CustomResolverFails) { false); EXPECT_THROW_WITH_MESSAGE( - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_), EnvoyException, - "STRICT_DNS clusters must NOT have a custom resolver name set"); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_), + EnvoyException, "STRICT_DNS clusters must NOT have a custom resolver name set"); } TEST_F(StrictDnsClusterImplTest, FailureRefreshRateBackoffResetsWhenSuccessHappens) { @@ -1404,9 +1410,9 @@ TEST_F(StrictDnsClusterImplTest, FailureRefreshRateBackoffResetsWhenSuccessHappe server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); - cluster.initialize([] {}); + cluster->initialize([] {}); // Failing response kicks the failure refresh backoff strategy. ON_CALL(random_, random()).WillByDefault(Return(8000)); @@ -1452,16 +1458,16 @@ TEST_F(StrictDnsClusterImplTest, TtlAsDnsRefreshRate) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_); ReadyWatcher membership_updated; - auto priority_update_cb = cluster.prioritySet().addPriorityUpdateCb( + auto priority_update_cb = cluster->prioritySet().addPriorityUpdateCb( [&](uint32_t, const HostVector&, const HostVector&) { membership_updated.ready(); return absl::OkStatus(); }); - cluster.initialize([] {}); + cluster->initialize([] {}); // TTL is recorded when the DNS response is successful and not empty EXPECT_CALL(membership_updated, ready()); @@ -1535,7 +1541,8 @@ TEST_F(StrictDnsClusterImplTest, Http2UserDefinedSettingsParametersValidation) { false); EXPECT_THROW_WITH_REGEX( - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_), EnvoyException, + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver_), + EnvoyException, R"(the \{hpack_table_size\} HTTP/2 SETTINGS parameter\(s\) can not be configured through)" " both"); } @@ -3751,9 +3758,9 @@ TEST_F(ClusterImplTest, CloseConnectionsOnHostHealthFailure) { Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver); + auto cluster = *StrictDnsClusterImpl::create(cluster_config, factory_context, dns_resolver); - EXPECT_TRUE(cluster.info()->features() & + EXPECT_TRUE(cluster->info()->features() & ClusterInfo::Features::CLOSE_CONNECTIONS_ON_HOST_HEALTH_FAILURE); } @@ -3961,7 +3968,9 @@ class ClusterInfoImplTest : public testing::Test { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - return std::make_unique(cluster_config_, factory_context, dns_resolver_); + return THROW_OR_RETURN_VALUE( + StrictDnsClusterImpl::create(cluster_config_, factory_context, dns_resolver_), + std::unique_ptr); } class RetryBudgetTestClusterInfo : public ClusterInfoImpl { diff --git a/test/extensions/clusters/aggregate/cluster_test.cc b/test/extensions/clusters/aggregate/cluster_test.cc index cd844a4c9167..06e8f5dae9e0 100644 --- a/test/extensions/clusters/aggregate/cluster_test.cc +++ b/test/extensions/clusters/aggregate/cluster_test.cc @@ -105,7 +105,10 @@ class AggregateClusterTest : public Event::TestUsingSimulatedTime, public testin server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - cluster_ = std::make_shared(cluster_config, config, factory_context); + absl::Status creation_status = absl::OkStatus(); + cluster_ = std::shared_ptr( + new Cluster(cluster_config, config, factory_context, creation_status)); + THROW_IF_NOT_OK(creation_status); server_context_.cluster_manager_.initializeThreadLocalClusters({"primary", "secondary"}); primary_.cluster_.info_->name_ = "primary"; diff --git a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc index beee813a9f12..f5356450fd73 100644 --- a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc +++ b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc @@ -58,8 +58,10 @@ class ClusterTest : public testing::Test, EXPECT_CALL(*dns_cache_manager_->dns_cache_, addUpdateCallbacks_(_)) .WillOnce(DoAll(SaveArgAddress(&update_callbacks_), Return(nullptr))); auto cache = dns_cache_manager_->getCache(config.dns_cache_config()).value(); - cluster_.reset( - new Cluster(cluster_config, std::move(cache), config, factory_context, this->get())); + absl::Status creation_status = absl::OkStatus(); + cluster_.reset(new Cluster(cluster_config, std::move(cache), config, factory_context, + this->get(), creation_status)); + THROW_IF_NOT_OK(creation_status); thread_aware_lb_ = std::make_unique(*cluster_); lb_factory_ = thread_aware_lb_->factory(); refreshLb(); diff --git a/test/extensions/clusters/eds/eds_speed_test.cc b/test/extensions/clusters/eds/eds_speed_test.cc index bfdd7d51173a..7c617ae67b46 100644 --- a/test/extensions/clusters/eds/eds_speed_test.cc +++ b/test/extensions/clusters/eds/eds_speed_test.cc @@ -101,7 +101,7 @@ class EdsSpeedTest { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - cluster_ = std::make_shared(eds_cluster_, factory_context); + cluster_ = *EdsClusterImpl::create(eds_cluster_, factory_context); EXPECT_EQ(initialize_phase, cluster_->initializePhase()); eds_callbacks_ = server_context_.cluster_manager_.subscription_factory_.callbacks_; subscription_ = std::make_unique( diff --git a/test/extensions/clusters/eds/eds_test.cc b/test/extensions/clusters/eds/eds_test.cc index 35e7fe25b0c7..10295bb4153c 100644 --- a/test/extensions/clusters/eds/eds_test.cc +++ b/test/extensions/clusters/eds/eds_test.cc @@ -133,7 +133,7 @@ class EdsTest : public testing::Test, public Event::TestUsingSimulatedTime { Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - cluster_ = std::make_shared(eds_cluster_, factory_context); + cluster_ = *EdsClusterImpl::create(eds_cluster_, factory_context); EXPECT_EQ(initialize_phase, cluster_->initializePhase()); eds_callbacks_ = server_context_.cluster_manager_.subscription_factory_.callbacks_; } @@ -2857,7 +2857,7 @@ class EdsCachedAssignmentTest : public testing::Test { ON_CALL(server_context_.cluster_manager_, edsResourcesCache()) .WillByDefault( Invoke([this]() -> Config::EdsResourcesCacheOptRef { return eds_resources_cache_; })); - cluster_pre_ = std::make_shared(eds_cluster_, factory_context); + cluster_pre_ = *EdsClusterImpl::create(eds_cluster_, factory_context); EXPECT_EQ(initialize_phase, cluster_pre_->initializePhase()); eds_callbacks_pre_ = server_context_.cluster_manager_.subscription_factory_.callbacks_; } @@ -2895,7 +2895,7 @@ class EdsCachedAssignmentTest : public testing::Test { Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - cluster_post_ = std::make_shared(eds_cluster_, factory_context); + cluster_post_ = *EdsClusterImpl::create(eds_cluster_, factory_context); // EXPECT_EQ(initialize_phase, cluster_post_->initializePhase()); eds_callbacks_post_ = server_context_.cluster_manager_.subscription_factory_.callbacks_; diff --git a/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc b/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc index d52c445ba018..65de11b49f41 100644 --- a/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc +++ b/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc @@ -55,8 +55,10 @@ class LogicalDnsClusterTest : public Event::TestUsingSimulatedTime, public testi Envoy::Upstream::ClusterFactoryContextImpl factory_context( server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); + absl::Status creation_status = absl::OkStatus(); cluster_ = std::shared_ptr( - new LogicalDnsCluster(cluster_config, factory_context, dns_resolver_)); + new LogicalDnsCluster(cluster_config, factory_context, dns_resolver_, creation_status)); + THROW_IF_NOT_OK(creation_status); priority_update_cb_ = cluster_->prioritySet().addPriorityUpdateCb( [&](uint32_t, const HostVector&, const HostVector&) { membership_updated_.ready(); diff --git a/test/extensions/clusters/redis/redis_cluster_test.cc b/test/extensions/clusters/redis/redis_cluster_test.cc index aec31c5529eb..d8236942e02f 100644 --- a/test/extensions/clusters/redis/redis_cluster_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_test.cc @@ -132,11 +132,11 @@ class RedisClusterTest : public testing::Test, Config::Utility::translateOpaqueConfig(cluster_config.cluster_type().typed_config(), ProtobufMessage::getStrictValidationVisitor(), config); cluster_callback_ = std::make_shared>(); - cluster_ = std::make_shared( + cluster_ = std::shared_ptr(*RedisCluster::create( cluster_config, TestUtility::downcastAndValidate< const envoy::extensions::clusters::redis::v3::RedisClusterConfig&>(config), - cluster_factory_context, *this, dns_resolver_, cluster_callback_); + cluster_factory_context, *this, dns_resolver_, cluster_callback_)); // This allows us to create expectation on cluster slot response without waiting for // makeRequest. pool_callbacks_ = cluster_->redis_discovery_session_.get(); diff --git a/test/integration/clusters/custom_static_cluster.h b/test/integration/clusters/custom_static_cluster.h index 8949effd534d..b540fba2b6e5 100644 --- a/test/integration/clusters/custom_static_cluster.h +++ b/test/integration/clusters/custom_static_cluster.h @@ -26,9 +26,11 @@ class CustomStaticCluster : public Upstream::ClusterImplBase { public: CustomStaticCluster(const envoy::config::cluster::v3::Cluster& cluster, Upstream::ClusterFactoryContext& context, uint32_t priority, - std::string address, uint32_t port) - : ClusterImplBase(cluster, context), priority_(priority), address_(std::move(address)), - port_(port), host_(makeHost()) {} + std::string address, uint32_t port, absl::Status& creation_status) + : ClusterImplBase(cluster, context, creation_status), priority_(priority), + address_(std::move(address)), port_(port), host_(makeHost()) { + THROW_IF_NOT_OK(creation_status); + } InitializePhase initializePhase() const override { return InitializePhase::Primary; } @@ -60,9 +62,11 @@ class CustomStaticClusterFactoryBase : public Upstream::ConfigurableClusterFacto createClusterWithConfig(const envoy::config::cluster::v3::Cluster& cluster, const test::integration::clusters::CustomStaticConfig& proto_config, Upstream::ClusterFactoryContext& context) override { - auto new_cluster = - std::make_shared(cluster, context, proto_config.priority(), - proto_config.address(), proto_config.port_value()); + absl::Status creation_status = absl::OkStatus(); + auto new_cluster = std::make_shared( + cluster, context, proto_config.priority(), proto_config.address(), + proto_config.port_value(), creation_status); + THROW_IF_NOT_OK(creation_status); return std::make_pair(new_cluster, create_lb_ ? new_cluster->threadAwareLb() : nullptr); } From b67557d64b4294bd7891e003134971ee70a02f14 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Tue, 2 Jul 2024 08:54:10 -0400 Subject: [PATCH 24/31] Update QUICHE from cf8d05ab4 to ee470ff42 (#35001) https://github.com/google/quiche/compare/cf8d05ab4..ee470ff42 ``` $ git log cf8d05ab4..ee470ff42 --date=short --no-merges --format="%ad %al %s" 2024-07-01 wub In `QuicBufferedPacketStoreTest`, pass `long_packet_type` to `QuicBufferedPacketStore::EnqueuePacket`. 2024-06-28 birenroy Removes BalsaFrame::InvalidCharsLevel::kWarning and related code. 2024-06-28 birenroy Removes tracking of invalid character counts in BalsaFrame. 2024-06-28 vasilvv Fix standalone QUICHE build 2024-06-28 martinduke Move MoqtTrack code into a .cc file. 2024-06-28 martinduke Handle MoQT ANNOUNCE_CANCEL messages. 2024-06-28 bnc Make default impl use quiche_feature_flags_list.h instead of quic_flags_list.h. 2024-06-27 quiche-dev Automated g4 rollback of changelist 646639268. 2024-06-26 quiche-dev Change the service type for WebView to "chromeipblinding" 2024-06-26 bnc Add `external_value` to quiche/common/quiche_feature_flags_list.h. 2024-06-25 wub Add flag count for `quic_bbr2_enable_bbpd_by_default`. 2024-06-25 birenroy Defines some string constants as `inline constexpr absl::string_view`. 2024-06-25 bnc Add test expectation of INVALID_HEADER_CHARACTER warning when header value has NULL. 2024-06-25 martinduke MoQT: Use object status for end of groups and SUBSCRIBE_DONE. 2024-06-24 birenroy Adds a `HttpValidationPolicy` option to sanitize or reject invalid whitespace in the HTTP first line. 2024-06-24 wub Fix a bug in libevent-based QuicEventLoop where artificial events are notified in the same epoll iteration. 2024-06-21 birenroy Adds a visitor-based metadata sending API to Http2Adapter and subclasses. ``` Signed-off-by: Ali Beyad --- bazel/external/quiche.BUILD | 1 + bazel/repository_locations.bzl | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index b1a80504bf14..5fac9e70fc9d 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -5519,6 +5519,7 @@ envoy_cc_library( ":quiche_balsa_balsa_enums_lib", ":quiche_balsa_header_api_lib", ":quiche_balsa_header_properties_lib", + ":quiche_balsa_http_validation_policy_lib", ":quiche_balsa_standard_header_map_lib", ":quiche_common_callbacks", ":quiche_common_platform_bug_tracker", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 949ae73e1238..0bcf5df48110 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1191,12 +1191,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "cf8d05ab435919878ca0db342e05e64620b43173", - sha256 = "a0b89cf43edb6e17126d536ee522127f7a71ee38d2813ba0464ed1a8c62db25f", + version = "ee470ff425efc46ddba7cce7c7355d0f706424b2", + sha256 = "c6fa6340783f0eff5db95ac0f38e1d7b465c4aa8a29c5e8cae5d2f64838be9f1", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2024-06-21", + release_date = "2024-07-01", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", From 277722e94187045bfe7da6d13d2835b5c5b14168 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 2 Jul 2024 10:30:38 -0400 Subject: [PATCH 25/31] http3: happy eyeballs (#34872) Adding happy eyeballs lite for HTTP/3 when going through the grid. This will try one v4 and one v6 address if the first two addresses have differing address families. Risk Level: medium Testing: new unit tests, e2e tests Docs Changes: inline Release Notes: inline [Optional Runtime guard:] yes Signed-off-by: Alyssa Wilk --- changelogs/current.yaml | 5 + .../upstream/connection_pooling.rst | 8 + source/common/http/conn_pool_grid.cc | 91 +++++++- source/common/http/conn_pool_grid.h | 31 ++- source/common/http/http3/conn_pool.cc | 41 +++- source/common/http/http3/conn_pool.h | 9 +- source/common/runtime/runtime_features.cc | 1 + test/common/http/conn_pool_grid_test.cc | 195 ++++++++++++++++-- test/common/http/http3/conn_pool_test.cc | 52 +++-- .../filters/http/dynamic_forward_proxy/BUILD | 1 + .../proxy_filter_integration_test.cc | 87 ++++++-- 11 files changed, 455 insertions(+), 66 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index a536f0e13030..d7a32fc14193 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -16,6 +16,11 @@ behavior_changes: change: | Changes the default value of ``envoy.reloadable_features.http2_use_oghttp2`` to true. This changes the codec used for HTTP/2 requests and responses. This behavior can be reverted by setting the feature to false. +- area: http3 + change: | + Added a "happy eyeballs" feature to HTTP/3 upstream, where it assuming happy eyeballs sorting results in alternating address + families will attempt the first v4 and v6 address before giving up on HTTP/3. This change can be reverted by setting + ``envoy.reloadable_features.http3_happy_eyeballs`` to false. - area: http2 change: | Passes HTTP/2 DATA frames through a different codec API. This behavior can be temporarily disabled by setting the runtime diff --git a/docs/root/intro/arch_overview/upstream/connection_pooling.rst b/docs/root/intro/arch_overview/upstream/connection_pooling.rst index d1ca3bd56c67..3d89d7d091ca 100644 --- a/docs/root/intro/arch_overview/upstream/connection_pooling.rst +++ b/docs/root/intro/arch_overview/upstream/connection_pooling.rst @@ -107,6 +107,14 @@ is still connecting, then a backup connection attempt will be made to the next a Eventually an attempt will succeed to one of the addresses in which case that connection will be used, or else all attempts will fail in which case a connection error will be reported. +HTTP/3 has limited Happy-Eyeballs-like support. +When using ref:`auto_config ` +for HTTP/3 with TCP-failover, Envoy will make a best-effort attempt to try two address families. As with TCP +Happy Eyeballs support, Envoy allows 300ms for the first HTTP/3 attempt to connect. If the connection explicitly +fails or the 300ms timeout expires, if DNS resolution results in the first two resolved addresses being of +different address families, a second HTTP/3 connection pool using the second address will be created and Envoy +will attempt to establish an HTTP/3 connection using the alternate address family. In this case HTTP/3 will only +be marked broken if TCP connectivity is established and both HTTP/3 connections fail. .. _arch_overview_conn_pool_how_many: diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index 89cecdcae22d..8249c204422f 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -11,6 +11,14 @@ namespace Envoy { namespace Http { +bool hasBothAddressFamilies(Upstream::HostConstSharedPtr host) { + Upstream::HostDescription::SharedConstAddressVector list_ = host->addressListOrNull(); + if (!list_ || list_->size() < 2 || !(*list_)[0]->ip() || !(*list_)[1]->ip()) { + return false; + } + return (*list_)[0]->ip()->version() != (*list_)[1]->ip()->version(); +} + namespace { absl::string_view describePool(const ConnectionPool::Instance& pool) { return pool.protocolDescription(); @@ -34,7 +42,7 @@ ConnectivityGrid::WrapperCallbacks::WrapperCallbacks(ConnectivityGrid& grid, const Instance::StreamOptions& options) : grid_(grid), decoder_(decoder), inner_callbacks_(&callbacks), next_attempt_timer_( - grid_.dispatcher_.createTimer([this]() -> void { tryAnotherConnection(); })), + grid_.dispatcher_.createTimer([this]() -> void { onNextAttemptTimer(); })), stream_options_(options) { if (!stream_options_.can_use_http3_) { // If alternate protocols are explicitly disabled, there must have been a failed request over @@ -71,18 +79,57 @@ void ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::onPoolFailu parent_.onConnectionAttemptFailed(this, reason, transport_failure_reason, host); } +void ConnectivityGrid::WrapperCallbacks::attemptSecondHttp3Connection() { + has_tried_http3_alternate_address_ = true; + auto attempt = + std::make_unique(*this, *grid_.getOrCreateHttp3AlternativePool()); + LinkedList::moveIntoList(std::move(attempt), connection_attempts_); + // Kick off a new stream attempt. + connection_attempts_.front()->newStream(); +} + +bool ConnectivityGrid::WrapperCallbacks::shouldAttemptSecondHttp3Connection() { + // Don't connect if the grid is shutting down. + if (grid_.destroying_) { + return false; + } + // Make sure each wrapper callbacks only tries HTTP/3 alternate addresses + // once. + if (has_tried_http3_alternate_address_) { + return false; + } + // Branch on reloadable flags. + if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http3_happy_eyeballs")) { + return false; + } + // QUIC "happy eyeballs" currently only handles one v4 and one v6 address. If + // there's not multiple families don't bother. + return hasBothAddressFamilies(grid_.host_); +} + void ConnectivityGrid::WrapperCallbacks::onConnectionAttemptFailed( ConnectionAttemptCallbacks* attempt, ConnectionPool::PoolFailureReason reason, absl::string_view transport_failure_reason, Upstream::HostDescriptionConstSharedPtr host) { ENVOY_LOG(trace, "{} pool failed to create connection to host '{}'.", describePool(attempt->pool()), host->hostname()); + grid_.dispatcher_.deferredDelete(attempt->removeFromList(connection_attempts_)); + if (grid_.isPoolHttp3(attempt->pool())) { - http3_attempt_failed_ = true; + if (shouldAttemptSecondHttp3Connection()) { + attemptSecondHttp3Connection(); + // Return - the above attempt will handle everything else. + return; + } + // HTTP/3 is only marked as failing if both the initial and any secondary + // attempt have both failed (i.e. the only remaining attempts are TCP). + if (connection_attempts_.empty() || + (connection_attempts_.size() == 1 && + &connection_attempts_.front()->pool() == grid_.http2_pool_.get())) { + http3_attempt_failed_ = true; + } } maybeMarkHttp3Broken(); - grid_.dispatcher_.deferredDelete(attempt->removeFromList(connection_attempts_)); - // If there is another connection attempt in flight then let that proceed. if (!connection_attempts_.empty()) { return; @@ -194,6 +241,15 @@ void ConnectivityGrid::WrapperCallbacks::cancelAllPendingAttempts( connection_attempts_.clear(); } +void ConnectivityGrid::WrapperCallbacks::onNextAttemptTimer() { + if (grid_.destroying_) { + return; + } + tryAnotherConnection(); + if (shouldAttemptSecondHttp3Connection()) { + attemptSecondHttp3Connection(); + } +} absl::optional ConnectivityGrid::WrapperCallbacks::tryAnotherConnection() { if (grid_.destroying_) { @@ -252,6 +308,7 @@ ConnectivityGrid::~ConnectivityGrid() { } http2_pool_.reset(); http3_pool_.reset(); + http3_alternate_pool_.reset(); } void ConnectivityGrid::deleteIsPending() { @@ -262,11 +319,22 @@ void ConnectivityGrid::deleteIsPending() { } } +ConnectionPool::Instance* ConnectivityGrid::getOrCreateHttp3AlternativePool() { + ASSERT(!deferred_deleting_); + ASSERT(!draining_); + if (http3_alternate_pool_ == nullptr) { + http3_alternate_pool_ = createHttp3Pool(true); + pools_.push_back(http3_alternate_pool_.get()); + setupPool(*http3_alternate_pool_.get()); + } + return http3_alternate_pool_.get(); +} + ConnectionPool::Instance* ConnectivityGrid::getOrCreateHttp3Pool() { ASSERT(!deferred_deleting_); ASSERT(!draining_); if (http3_pool_ == nullptr) { - http3_pool_ = createHttp3Pool(); + http3_pool_ = createHttp3Pool(false); pools_.push_back(http3_pool_.get()); setupPool(*http3_pool_.get()); } @@ -291,11 +359,12 @@ ConnectionPool::InstancePtr ConnectivityGrid::createHttp2Pool() { origin_, alternate_protocols_); } -ConnectionPool::InstancePtr ConnectivityGrid::createHttp3Pool() { - return Http3::allocateConnPool( - dispatcher_, random_generator_, host_, priority_, options_, transport_socket_options_, state_, - quic_stat_names_, *alternate_protocols_, scope_, - makeOptRefFromPtr(this), quic_info_); +ConnectionPool::InstancePtr ConnectivityGrid::createHttp3Pool(bool attempt_alternate_address) { + return Http3::allocateConnPool(dispatcher_, random_generator_, host_, priority_, options_, + transport_socket_options_, state_, quic_stat_names_, + *alternate_protocols_, scope_, + makeOptRefFromPtr(this), + quic_info_, attempt_alternate_address); } void ConnectivityGrid::setupPool(ConnectionPool::Instance& pool) { @@ -373,7 +442,7 @@ bool ConnectivityGrid::maybePreconnect(float) { } bool ConnectivityGrid::isPoolHttp3(const ConnectionPool::Instance& pool) { - return &pool == http3_pool_.get(); + return &pool == http3_pool_.get() || &pool == http3_alternate_pool_.get(); } HttpServerPropertiesCache::Http3StatusTracker& ConnectivityGrid::getHttp3StatusTracker() const { diff --git a/source/common/http/conn_pool_grid.h b/source/common/http/conn_pool_grid.h index b0b9064225d2..d23a9992b19b 100644 --- a/source/common/http/conn_pool_grid.h +++ b/source/common/http/conn_pool_grid.h @@ -22,6 +22,10 @@ namespace Http { // Any cancel call on the wrapper callbacks cancels either or both stream // attempts to the wrapped pools. the wrapper callbacks will only pass up // failure if both HTTP/3 and HTTP/2 attempts fail. +// +// The grid also handles HTTP/3 "happy eyeballs" which is a best-effort attempt +// to try using one IPv4 address and one IPv6 address if both families exist in +// the host's address list. class ConnectivityGrid : public ConnectionPool::Instance, public Http3::PoolConnectResultCallback, protected Logger::Loggable { @@ -99,6 +103,12 @@ class ConnectivityGrid : public ConnectionPool::Instance, // connection has been attempted, an empty optional otherwise. absl::optional tryAnotherConnection(); + // This timer is registered when an initial HTTP/3 attempt is started. + // The timeout for TCP failover and HTTP/3 happy eyeballs are the same, so + // when this timer fires it's possible that two additional connections will + // be kicked off. + void onNextAttemptTimer(); + // Called by a ConnectionAttempt when the underlying pool fails. void onConnectionAttemptFailed(ConnectionAttemptCallbacks* attempt, ConnectionPool::PoolFailureReason reason, @@ -118,6 +128,15 @@ class ConnectivityGrid : public ConnectionPool::Instance, Upstream::HostDescriptionConstSharedPtr host); private: + // Called if the initial HTTP/3 connection fails. + // Returns true if an HTTP/3 happy eyeballs attempt can be kicked off + // (runtime guard is on, IPv6 and IPv6 addresses are present, happy eyeballs + // has not been tried yet for this wrapper, grid is not in shutdown). + bool shouldAttemptSecondHttp3Connection(); + // This kicks off an HTTP/3 happy eyeballs attempt, connecting to the second + // address in the host's address list. + void attemptSecondHttp3Connection(); + // Removes this from the owning list, deleting it. void deleteThis(); @@ -143,6 +162,10 @@ class ConnectivityGrid : public ConnectionPool::Instance, Event::TimerPtr next_attempt_timer_; // Checks if http2 has been attempted. bool has_attempted_http2_ = false; + // Checks if "happy eyeballs" has been done for HTTP/3. This largely makes + // sure that if we kick off a secondary attempt due to timeout we don't kick + // off another one if the original HTTP/3 connection explicitly fails. + bool has_tried_http3_alternate_address_ = false; // True if the HTTP/3 attempt failed. bool http3_attempt_failed_{}; // True if the TCP attempt succeeded. @@ -218,8 +241,11 @@ class ConnectivityGrid : public ConnectionPool::Instance, // Returns the specified pool, which will be created if necessary ConnectionPool::Instance* getOrCreateHttp3Pool(); ConnectionPool::Instance* getOrCreateHttp2Pool(); + ConnectionPool::Instance* getOrCreateHttp3AlternativePool(); - virtual ConnectionPool::InstancePtr createHttp3Pool(); + // True if this pool is the "happy eyeballs" attempt, and should use the + // secondary address family. + virtual ConnectionPool::InstancePtr createHttp3Pool(bool attempt_alternate_address); virtual ConnectionPool::InstancePtr createHttp2Pool(); // This batch of member variables are latched objects required for pool creation. @@ -238,6 +264,9 @@ class ConnectivityGrid : public ConnectionPool::Instance, // The connection pools to use to create new streams ConnectionPool::InstancePtr http3_pool_; + // This is the pool used for the HTTP/3 "happy eyeballs" attempt. If it is + // created it will have the opposite address family from http3_pool_ above. + ConnectionPool::InstancePtr http3_alternate_pool_; ConnectionPool::InstancePtr http2_pool_; // A convenience vector to allow taking actions on all pools. absl::InlinedVector pools_; diff --git a/source/common/http/http3/conn_pool.cc b/source/common/http/http3/conn_pool.cc index 89dd8aa882cb..236dad0eb550 100644 --- a/source/common/http/http3/conn_pool.cc +++ b/source/common/http/http3/conn_pool.cc @@ -17,6 +17,27 @@ namespace Http { namespace Http3 { namespace { +// Assuming here that happy eyeballs sorting results in the first address being +// a different address family than the second, make a best effort attempt to +// return a secondary address family. +Network::Address::InstanceConstSharedPtr +getHostAddress(std::shared_ptr host, + bool secondary_address_family) { + if (!secondary_address_family) { + return host->address(); + } + Upstream::HostDescription::SharedConstAddressVector list_or_null = host->addressListOrNull(); + if (!list_or_null || list_or_null->size() < 2 || !(*list_or_null)[0]->ip() || + !(*list_or_null)[1]->ip()) { + // This function is only called if the parallel address checks in conn_pool_grid.cc + // already passed so it should not return here, but if there was a DNS + // re-resolve between checking the address list in the grid and checking + // here, we'll fail over here rather than fast-fail the connection. + return host->address(); + } + return (*list_or_null)[1]; +} + uint32_t getMaxStreams(const Upstream::ClusterInfo& cluster) { return PROTOBUF_GET_WRAPPED_OR_DEFAULT(cluster.http3Options().quic_protocol_options(), max_concurrent_streams, 100); @@ -87,13 +108,14 @@ Http3ConnPoolImpl::Http3ConnPoolImpl( const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, Random::RandomGenerator& random_generator, Upstream::ClusterConnectivityState& state, CreateClientFn client_fn, CreateCodecFn codec_fn, std::vector protocol, - OptRef connect_callback, Http::PersistentQuicInfo& quic_info) + OptRef connect_callback, Http::PersistentQuicInfo& quic_info, + bool attempt_happy_eyeballs) : FixedHttpConnPoolImpl(host, priority, dispatcher, options, transport_socket_options, random_generator, state, client_fn, codec_fn, protocol, {}, nullptr), quic_info_(dynamic_cast(quic_info)), server_id_(sni(transport_socket_options, host), static_cast(host_->address()->ip()->port()), false), - connect_callback_(connect_callback) {} + connect_callback_(connect_callback), attempt_happy_eyeballs_(attempt_happy_eyeballs) {} void Http3ConnPoolImpl::onConnected(Envoy::ConnectionPool::ActiveClient&) { if (connect_callback_ != absl::nullopt) { @@ -121,19 +143,20 @@ Http3ConnPoolImpl::createClientConnection(Quic::QuicStatNames& quic_stat_names, return nullptr; // no secrets available yet. } + Network::Address::InstanceConstSharedPtr address = + getHostAddress(host(), attempt_happy_eyeballs_); auto upstream_local_address_selector = host()->cluster().getUpstreamLocalAddressSelector(); auto upstream_local_address = - upstream_local_address_selector->getUpstreamLocalAddress(host()->address(), socketOptions()); + upstream_local_address_selector->getUpstreamLocalAddress(address, socketOptions()); auto source_address = upstream_local_address.address_; if (source_address == nullptr) { - auto host_address = host()->address(); - source_address = Network::Utility::getLocalAddress(host_address->ip()->version()); + source_address = Network::Utility::getLocalAddress(address->ip()->version()); } return Quic::createQuicNetworkConnection( - quic_info_, std::move(crypto_config), server_id_, dispatcher(), host()->address(), - source_address, quic_stat_names, rtt_cache, scope, upstream_local_address.socket_options_, + quic_info_, std::move(crypto_config), server_id_, dispatcher(), address, source_address, + quic_stat_names, rtt_cache, scope, upstream_local_address.socket_options_, transportSocketOptions(), connection_id_generator_, host_->transportSocketFactory()); } @@ -145,7 +168,7 @@ allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_ Upstream::ClusterConnectivityState& state, Quic::QuicStatNames& quic_stat_names, OptRef rtt_cache, Stats::Scope& scope, OptRef connect_callback, - Http::PersistentQuicInfo& quic_info) { + Http::PersistentQuicInfo& quic_info, bool attempt_happy_eyeballs) { return std::make_unique( host, priority, dispatcher, options, transport_socket_options, random_generator, state, [&quic_stat_names, rtt_cache, @@ -185,7 +208,7 @@ allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_ auto_connect); return codec; }, - std::vector{Protocol::Http3}, connect_callback, quic_info); + std::vector{Protocol::Http3}, connect_callback, quic_info, attempt_happy_eyeballs); } } // namespace Http3 diff --git a/source/common/http/http3/conn_pool.h b/source/common/http/http3/conn_pool.h index 29d3b81e579b..932537a8a0ee 100644 --- a/source/common/http/http3/conn_pool.h +++ b/source/common/http/http3/conn_pool.h @@ -146,7 +146,7 @@ class Http3ConnPoolImpl : public FixedHttpConnPoolImpl { Upstream::ClusterConnectivityState& state, CreateClientFn client_fn, CreateCodecFn codec_fn, std::vector protocol, OptRef connect_callback, - Http::PersistentQuicInfo& quic_info); + Http::PersistentQuicInfo& quic_info, bool attempt_happy_eyeballs = false); ~Http3ConnPoolImpl() override; ConnectionPool::Cancellable* newStream(Http::ResponseDecoder& response_decoder, @@ -178,6 +178,11 @@ class Http3ConnPoolImpl : public FixedHttpConnPoolImpl { quic::DeterministicConnectionIdGenerator connection_id_generator_{ quic::kQuicDefaultConnectionIdLength}; + + // Make a best effort attempt to find an address family other than the initial + // address. This fails over to using the primary address if the second address + // in the list isn't of a different address family. + bool attempt_happy_eyeballs_; }; std::unique_ptr @@ -188,7 +193,7 @@ allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_ Upstream::ClusterConnectivityState& state, Quic::QuicStatNames& quic_stat_names, OptRef rtt_cache, Stats::Scope& scope, OptRef connect_callback, - Http::PersistentQuicInfo& quic_info); + Http::PersistentQuicInfo& quic_info, bool attempt_happy_eyeballs = false); } // namespace Http3 } // namespace Http diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index bcd7edac129e..b39d8a191ae8 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -59,6 +59,7 @@ RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header); RUNTIME_GUARD(envoy_reloadable_features_http2_use_oghttp2); RUNTIME_GUARD(envoy_reloadable_features_http2_use_visitor_for_data); RUNTIME_GUARD(envoy_reloadable_features_http2_validate_authority_with_quiche); +RUNTIME_GUARD(envoy_reloadable_features_http3_happy_eyeballs); RUNTIME_GUARD(envoy_reloadable_features_http_filter_avoid_reentrant_local_reply); // Delay deprecation and decommission until UHV is enabled. RUNTIME_GUARD(envoy_reloadable_features_http_reject_path_with_fragment); diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index dca889708e5a..d411b97a23dc 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -51,8 +51,42 @@ class ConnectivityGridForTest : public ConnectivityGrid { return grid.getOrCreateHttp2Pool(); } - ConnectionPool::InstancePtr createHttp3Pool() override { return createMockPool("http3"); } + ConnectionPool::InstancePtr createHttp3Pool(bool alternate) override { + if (!alternate) { + return createMockPool("http3"); + } + ASSERT(!http3_alternate_pool_); + ConnectionPool::InstancePtr ret = createMockPool("alternate"); + ON_CALL(*static_cast(ret.get()), newStream(_, _, _)) + .WillByDefault(Invoke( + [&](Http::ResponseDecoder&, ConnectionPool::Callbacks& callbacks, + const ConnectionPool::Instance::StreamOptions&) -> ConnectionPool::Cancellable* { + if (!alternate_immediate_) { + callbacks_.push_back(&callbacks); + return cancel_; + } + if (alternate_failure_) { + callbacks.onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, + "reason", host()); + } else { + callbacks.onPoolReady(*encoder_, host(), *info_, absl::nullopt); + } + return nullptr; + })); + return ret; + } ConnectionPool::InstancePtr createHttp2Pool() override { return createMockPool("http2"); } + + void createHttp3AlternatePool() { + ConnectionPool::MockInstance* instance = new NiceMock(); + setupPool(*instance); + http3_alternate_pool_.reset(instance); + pools_.push_back(instance); + EXPECT_CALL(*instance, protocolDescription()) + .Times(AnyNumber()) + .WillRepeatedly(Return("alternate")); + } + ConnectionPool::InstancePtr createMockPool(absl::string_view type) { ConnectionPool::MockInstance* instance = new NiceMock(); ON_CALL(*instance, newStream(_, _, _)) @@ -89,6 +123,9 @@ class ConnectivityGridForTest : public ConnectivityGrid { ConnectionPool::MockInstance* http2Pool() { return static_cast(http2_pool_.get()); } + ConnectionPool::MockInstance* alternate() { + return static_cast(http3_alternate_pool_.get()); + } ConnectionPool::Callbacks* callbacks(int index = 0) { return callbacks_[index]; } @@ -109,6 +146,9 @@ class ConnectivityGridForTest : public ConnectivityGrid { bool immediate_success_{}; bool immediate_failure_{}; bool second_pool_immediate_success_{}; + + bool alternate_immediate_{true}; + bool alternate_failure_{true}; }; namespace { @@ -124,6 +164,9 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin quic_stat_names_(store_.symbolTable()) { ON_CALL(factory_context_.server_context_, threadLocal()) .WillByDefault(ReturnRef(thread_local_)); + // Make sure we test happy eyeballs code. + address_list_ = {*Network::Utility::resolveUrl("tcp://127.0.0.1:9000"), + *Network::Utility::resolveUrl("tcp://[::]:9000")}; } void initialize() { @@ -133,15 +176,17 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin #else std::make_unique(); #endif + host_ = std::make_shared( + cluster_, "hostname", *Network::Utility::resolveUrl("tcp://127.0.0.1:9000"), nullptr, + nullptr, 1, envoy::config::core::v3::Locality(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN, simTime(), address_list_); grid_ = std::make_unique( - dispatcher_, random_, - Upstream::makeTestHost(cluster_, "hostname", "tcp://127.0.0.1:9000", simTime()), - Upstream::ResourcePriority::Default, socket_options_, transport_socket_options_, state_, - simTime(), alternate_protocols_, options_, quic_stat_names_, *store_.rootScope(), - *quic_connection_persistent_info_); + dispatcher_, random_, host_, Upstream::ResourcePriority::Default, socket_options_, + transport_socket_options_, state_, simTime(), alternate_protocols_, options_, + quic_stat_names_, *store_.rootScope(), *quic_connection_persistent_info_); grid_->cancel_ = &cancel_; - host_ = grid_->host(); grid_->info_ = &info_; grid_->encoder_ = &encoder_; } @@ -178,7 +223,8 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin Quic::QuicStatNames quic_stat_names_; PersistentQuicInfoPtr quic_connection_persistent_info_; NiceMock cancel_; - Upstream::HostDescriptionConstSharedPtr host_; + std::shared_ptr host_; + Upstream::HostDescriptionImpl::AddressVector address_list_{}; NiceMock callbacks_; NiceMock decoder_; @@ -229,7 +275,7 @@ TEST_F(ConnectivityGridTest, ImmediateSuccess) { } // Test the first pool failing and the second connecting. -TEST_F(ConnectivityGridTest, FailureThenSuccessSerial) { +TEST_F(ConnectivityGridTest, DoubleFailureThenSuccessSerial) { initialize(); addHttp3AlternateProtocol(); EXPECT_EQ(grid_->http3Pool(), nullptr); @@ -248,6 +294,7 @@ TEST_F(ConnectivityGridTest, FailureThenSuccessSerial) { EXPECT_LOG_CONTAINS_ALL_OF( Envoy::ExpectedLogMessages( {{"trace", "http3 pool failed to create connection to host 'hostname'"}, + {"trace", "alternate pool failed to create connection to host 'hostname'"}, {"trace", "http2 pool attempting to create a new stream to host 'hostname'"}}), grid_->callbacks()->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, "reason", host_)); @@ -261,8 +308,111 @@ TEST_F(ConnectivityGridTest, FailureThenSuccessSerial) { EXPECT_TRUE(grid_->isHttp3Broken()); } +// Test all three connections in parallel, H3 failing and TCP connecting. +TEST_F(ConnectivityGridTest, ParallelConnectionsTcpConnects) { + initialize(); + grid_->alternate_immediate_ = false; + addHttp3AlternateProtocol(); + EXPECT_EQ(grid_->http3Pool(), nullptr); + + // This timer will be returned and armed as the grid creates the wrapper's failover timer. + Event::MockTimer* failover_timer = new StrictMock(&dispatcher_); + EXPECT_CALL(*failover_timer, enableTimer(std::chrono::milliseconds(300), nullptr)).Times(2); + EXPECT_CALL(*failover_timer, enabled()).WillRepeatedly(Return(false)); + + EXPECT_LOG_CONTAINS("trace", "http3 pool attempting to create a new stream to host 'hostname'", + grid_->newStream(decoder_, callbacks_, + {/*can_send_early_data=*/false, + /*can_use_http3_=*/true})); + + EXPECT_NE(grid_->http3Pool(), nullptr); + EXPECT_EQ(grid_->http2Pool(), nullptr); + EXPECT_EQ(grid_->alternate(), nullptr); + + // The failover timer should kick off H3 alternate and H2 + failover_timer->invokeCallback(); + EXPECT_NE(grid_->http2Pool(), nullptr); + EXPECT_NE(grid_->alternate(), nullptr); + + EXPECT_CALL(callbacks_.pool_failure_, ready()).Times(0); + // Fail the alternate pool. H3 should not be broken. + grid_->callbacks(2)->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, + "reason", host_); + EXPECT_FALSE(grid_->isHttp3Broken()); + + // Fail the H3 pool. H3 should still not be broken as TCP has not connected. + grid_->callbacks()->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, + "reason", host_); + EXPECT_FALSE(grid_->isHttp3Broken()); + + // Now TCP connects. H3 should be marked broken. + // onPoolReady should be passed from the pool back to the original caller. + ASSERT_NE(grid_->callbacks(), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + EXPECT_LOG_CONTAINS("trace", "http2 pool successfully connected to host 'hostname'", + grid_->callbacks(1)->onPoolReady(encoder_, host_, info_, absl::nullopt)); + EXPECT_TRUE(grid_->isHttp3Broken()); +} + +// Test the first pool failing inline but http/3 happy eyeballs succeeding inline +TEST_F(ConnectivityGridTest, H3HappyEyeballsMeansNoH2Pool) { + // The alternate H3 pool will succeed inline. + initialize(); + grid_->alternate_failure_ = false; + grid_->immediate_failure_ = true; + addHttp3AlternateProtocol(); + EXPECT_EQ(grid_->http3Pool(), nullptr); + + EXPECT_CALL(callbacks_.pool_ready_, ready()); + EXPECT_EQ(nullptr, grid_->newStream(decoder_, callbacks_, + {/*can_send_early_data=*/false, + /*can_use_http3_=*/true})); + + EXPECT_NE(grid_->http3Pool(), nullptr); + EXPECT_NE(grid_->alternate(), nullptr); + EXPECT_EQ(grid_->http2Pool(), nullptr); +} + +// Test both connections happening in parallel and the second connecting. +TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelH2Connects) { + initialize(); + addHttp3AlternateProtocol(); + EXPECT_EQ(grid_->http3Pool(), nullptr); + + // This timer will be returned and armed as the grid creates the wrapper's failover timer. + Event::MockTimer* failover_timer = new StrictMock(&dispatcher_); + EXPECT_CALL(*failover_timer, enableTimer(std::chrono::milliseconds(300), nullptr)).Times(2); + EXPECT_CALL(*failover_timer, enabled()).WillRepeatedly(Return(false)); + + grid_->newStream(decoder_, callbacks_, + {/*can_send_early_data_=*/false, + /*can_use_http3_=*/true}); + EXPECT_NE(grid_->http3Pool(), nullptr); + EXPECT_TRUE(failover_timer->enabled_); + + // Kick off the second connection. + failover_timer->invokeCallback(); + EXPECT_NE(grid_->http2Pool(), nullptr); + // QUIC happy eyeballs fails inline by default but should be tried. + EXPECT_NE(grid_->alternate(), nullptr); + + // onPoolFailure should not be passed up the first time. Instead the grid + // should wait on the second pool. + EXPECT_CALL(callbacks_.pool_failure_, ready()).Times(0); + grid_->callbacks()->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, + "reason", host_); + + // onPoolReady should be passed from the pool back to the original caller. + EXPECT_NE(grid_->callbacks(), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + grid_->callbacks(1)->onPoolReady(encoder_, host_, info_, absl::nullopt); + EXPECT_TRUE(grid_->isHttp3Broken()); +} + // Test both connections happening in parallel and the second connecting. -TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelSecondConnects) { +TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelH2ConnectsNoHE) { + address_list_ = {*Network::Utility::resolveUrl("tcp://127.0.0.1:9000"), + *Network::Utility::resolveUrl("tcp://127.0.0.1:9001")}; initialize(); addHttp3AlternateProtocol(); EXPECT_EQ(grid_->http3Pool(), nullptr); @@ -281,6 +431,9 @@ TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelSecondConnects) { // Kick off the second connection. failover_timer->invokeCallback(); EXPECT_NE(grid_->http2Pool(), nullptr); + // Unlike the test above, QUIC happy eyeballs should not be tried because + // there's only one address family. + EXPECT_EQ(grid_->alternate(), nullptr); // onPoolFailure should not be passed up the first time. Instead the grid // should wait on the second pool. @@ -316,11 +469,12 @@ TEST_F(ConnectivityGridTest, SrttMatters) { cancel->cancel(Envoy::ConnectionPool::CancelPolicy::CloseExcess); } -// Test both connections happening in parallel and the first connecting. +// Test multiple connections happening in parallel and the first connecting. TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelFirstConnects) { initialize(); addHttp3AlternateProtocol(); EXPECT_EQ(grid_->http3Pool(), nullptr); + grid_->alternate_immediate_ = false; // This timer will be returned and armed as the grid creates the wrapper's failover timer. Event::MockTimer* failover_timer = new NiceMock(&dispatcher_); @@ -332,12 +486,17 @@ TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelFirstConnects) { EXPECT_TRUE(failover_timer->enabled_); - // Kick off the second connection. + // Kick off the second and third connections. failover_timer->invokeCallback(); EXPECT_NE(grid_->http2Pool(), nullptr); + EXPECT_NE(grid_->alternate(), nullptr); // onPoolFailure should not be passed up the first time. Instead the grid - // should wait on the other pool + // should wait on the other pools + EXPECT_CALL(callbacks_.pool_failure_, ready()).Times(0); + grid_->callbacks(2)->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, + "reason", host_); + EXPECT_CALL(callbacks_.pool_failure_, ready()).Times(0); grid_->callbacks(1)->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, "reason", host_); @@ -349,7 +508,7 @@ TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelFirstConnects) { EXPECT_FALSE(grid_->isHttp3Broken()); } -// Test both connections happening in parallel and the second connecting before +// Test two connections happening in parallel and the second connecting before // the first eventually fails. TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelSecondConnectsFirstFail) { initialize(); @@ -495,7 +654,7 @@ TEST_F(ConnectivityGridTest, FailureThenSuccessForMultipleConnectionsSerial) { cancel2->cancel(Envoy::ConnectionPool::CancelPolicy::CloseExcess); } -// Test double failure under the stack of newStream. +// Test triple failure under the stack of newStream. TEST_F(ConnectivityGridTest, ImmediateDoubleFailure) { initialize(); addHttp3AlternateProtocol(); @@ -617,12 +776,15 @@ TEST_F(ConnectivityGridTest, DrainCallbacks) { grid_->drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainExistingConnections); } + grid_->createHttp3AlternatePool(); // The second time a drain is started, both pools should still be notified. { EXPECT_CALL(*grid_->http3Pool(), drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); EXPECT_CALL(*grid_->http2Pool(), drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); + EXPECT_CALL(*grid_->alternate(), + drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); grid_->drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete); } { @@ -635,10 +797,11 @@ TEST_F(ConnectivityGridTest, DrainCallbacks) { } { - // Notify the grid that another pool has been drained. Now that all pools are + // Notify the grid that the remaining pools have been drained. Now that all pools are // drained, the original callers should be informed. EXPECT_FALSE(drain_received); EXPECT_CALL(*grid_->http3Pool(), isIdle()).WillRepeatedly(Return(true)); + EXPECT_CALL(*grid_->alternate(), isIdle()).WillRepeatedly(Return(true)); grid_->http3Pool()->idle_cb_(); EXPECT_TRUE(drain_received); } diff --git a/test/common/http/http3/conn_pool_test.cc b/test/common/http/http3/conn_pool_test.cc index 1c92a772a7e0..f45016d4c7ac 100644 --- a/test/common/http/http3/conn_pool_test.cc +++ b/test/common/http/http3/conn_pool_test.cc @@ -54,6 +54,7 @@ class Http3ConnPoolImplTest : public Event::TestUsingSimulatedTime, public testi EXPECT_CALL(mockHost(), transportSocketFactory()).WillRepeatedly(testing::ReturnRef(*factory_)); EXPECT_CALL(mockHost().cluster_, connectTimeout()) .WillRepeatedly(Return(std::chrono::milliseconds(10000))); + ON_CALL(mockHost(), addressListOrNull).WillByDefault(testing::Return(address_list_)); new Event::MockSchedulableCallback(&dispatcher_); Network::ConnectionSocket::OptionsSharedPtr options = std::make_shared(); @@ -64,13 +65,16 @@ class Http3ConnPoolImplTest : public Event::TestUsingSimulatedTime, public testi return Upstream::UpstreamLocalAddress({nullptr, nullptr}); })); Network::TransportSocketOptionsConstSharedPtr transport_options; - pool_ = allocateConnPool( - dispatcher_, random_, host_, Upstream::ResourcePriority::Default, options, - transport_options, state_, quic_stat_names_, {}, *store_.rootScope(), - makeOptRef(connect_result_callback_), quic_info_); + pool_ = + allocateConnPool(dispatcher_, random_, host_, Upstream::ResourcePriority::Default, options, + transport_options, state_, quic_stat_names_, {}, *store_.rootScope(), + makeOptRef(connect_result_callback_), + quic_info_, happy_eyeballs_); EXPECT_EQ(3000, Http3ConnPoolImplPeer::getServerId(*pool_).port()); } + void createNewStream(); + Upstream::MockHost& mockHost() { return static_cast(*host_); } NiceMock dispatcher_; @@ -80,6 +84,10 @@ class Http3ConnPoolImplTest : public Event::TestUsingSimulatedTime, public testi Upstream::ClusterConnectivityState state_; Network::Address::InstanceConstSharedPtr test_address_ = *Network::Utility::resolveUrl("tcp://127.0.0.1:3000"); + std::shared_ptr address_list_{ + new Upstream::HostDescription::AddressVector{ + *Network::Utility::resolveUrl("tcp://127.0.0.1:3000"), + *Network::Utility::resolveUrl("tcp://[::]:3000")}}; NiceMock context_; std::unique_ptr factory_; Ssl::ClientContextSharedPtr ssl_context_{new Ssl::MockClientContext()}; @@ -90,6 +98,7 @@ class Http3ConnPoolImplTest : public Event::TestUsingSimulatedTime, public testi std::shared_ptr socket_option_{new Network::MockSocketOption()}; testing::NiceMock thread_local_; absl::Status creation_status_; + bool happy_eyeballs_ = false; }; class MockQuicClientTransportSocketFactory : public Quic::QuicClientTransportSocketFactory { @@ -160,7 +169,8 @@ TEST_F(Http3ConnPoolImplTest, FailWithSecretsBecomeEmpty) { nullptr); } -TEST_F(Http3ConnPoolImplTest, CreationAndNewStream) { +void Http3ConnPoolImplTest::createNewStream() { + EXPECT_CALL(mockHost(), address()).WillRepeatedly(Return(test_address_)); initialize(); MockResponseDecoder decoder; @@ -169,13 +179,18 @@ TEST_F(Http3ConnPoolImplTest, CreationAndNewStream) { std::shared_ptr cluster_socket_option{new Network::MockSocketOption()}; mockHost().cluster_.cluster_socket_options_->push_back(cluster_socket_option); EXPECT_CALL(*mockHost().cluster_.upstream_local_address_selector_, getUpstreamLocalAddressImpl(_)) - .WillOnce(Invoke( - [&](const Network::Address::InstanceConstSharedPtr&) -> Upstream::UpstreamLocalAddress { - Network::ConnectionSocket::OptionsSharedPtr options = - std::make_shared(); - Network::Socket::appendOptions(options, mockHost().cluster_.cluster_socket_options_); - return Upstream::UpstreamLocalAddress({nullptr, options}); - })); + .WillOnce(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> Upstream::UpstreamLocalAddress { + if (happy_eyeballs_ && address_list_->size() == 2) { + EXPECT_EQ(address, (*address_list_)[1]); + } else { + EXPECT_EQ(address, test_address_); + } + Network::ConnectionSocket::OptionsSharedPtr options = + std::make_shared(); + Network::Socket::appendOptions(options, mockHost().cluster_.cluster_socket_options_); + return Upstream::UpstreamLocalAddress({nullptr, options}); + })); EXPECT_CALL(*cluster_socket_option, setOption(_, _)).Times(3u); EXPECT_CALL(*socket_option_, setOption(_, _)).Times(3u); // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) @@ -196,6 +211,19 @@ TEST_F(Http3ConnPoolImplTest, CreationAndNewStream) { pool_->onConnectionEvent(*clients.front(), "", Network::ConnectionEvent::Connected); } +TEST_F(Http3ConnPoolImplTest, CreationAndNewStream) { createNewStream(); } + +TEST_F(Http3ConnPoolImplTest, CreationAndNewHappyEyeballsStream) { + happy_eyeballs_ = true; + createNewStream(); +} + +TEST_F(Http3ConnPoolImplTest, CreationAndFailHappyEyeballs) { + address_list_->pop_back(); + happy_eyeballs_ = true; + createNewStream(); +} + TEST_F(Http3ConnPoolImplTest, NewAndCancelStreamBeforeConnect) { initialize(); diff --git a/test/extensions/filters/http/dynamic_forward_proxy/BUILD b/test/extensions/filters/http/dynamic_forward_proxy/BUILD index a8f2c28ba77d..c62c88bb386e 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/BUILD +++ b/test/extensions/filters/http/dynamic_forward_proxy/BUILD @@ -87,6 +87,7 @@ envoy_extension_cc_test( "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc index 803eda5d3841..4cfa502e3e26 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -1,6 +1,7 @@ #include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" +#include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.h" #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" #include "source/common/tls/context_config_impl.h" @@ -97,26 +98,28 @@ name: stream-info-to-headers-filter ? envoy::config::cluster::v3::Cluster_DnsLookupFamily::Cluster_DnsLookupFamily_V4_ONLY : envoy::config::cluster::v3::Cluster_DnsLookupFamily::Cluster_DnsLookupFamily_V6_ONLY); - ConfigHelper::HttpProtocolOptions protocol_options; - protocol_options.mutable_upstream_http_protocol_options()->set_auto_sni(true); + protocol_options_.mutable_upstream_http_protocol_options()->set_auto_sni(true); if (!override_auto_sni_header.empty()) { - protocol_options.mutable_upstream_http_protocol_options()->set_override_auto_sni_header( + protocol_options_.mutable_upstream_http_protocol_options()->set_override_auto_sni_header( override_auto_sni_header); } - protocol_options.mutable_upstream_http_protocol_options()->set_auto_san_validation(true); + protocol_options_.mutable_upstream_http_protocol_options()->set_auto_san_validation(true); if (upstreamProtocol() == Http::CodecType::HTTP1) { - protocol_options.mutable_explicit_http_config()->mutable_http_protocol_options(); - } else { - ASSERT(upstreamProtocol() == Http::CodecType::HTTP2); - protocol_options.mutable_explicit_http_config()->mutable_http2_protocol_options(); + protocol_options_.mutable_explicit_http_config()->mutable_http_protocol_options(); + ConfigHelper::setProtocolOptions(cluster_, protocol_options_); + } else if (upstreamProtocol() == Http::CodecType::HTTP2) { + protocol_options_.mutable_explicit_http_config()->mutable_http2_protocol_options(); if (low_stream_limits_) { - protocol_options.mutable_explicit_http_config() + protocol_options_.mutable_explicit_http_config() ->mutable_http2_protocol_options() ->mutable_max_concurrent_streams() ->set_value(1); } + ConfigHelper::setProtocolOptions(cluster_, protocol_options_); + } else { + ASSERT(!low_stream_limits_); + // H3 config is set below after fake upstream creation. } - ConfigHelper::setProtocolOptions(cluster_, protocol_options); if (low_stream_limits_) { envoy::config::cluster::v3::CircuitBreakers* circuit_breakers = @@ -130,8 +133,15 @@ name: stream-info-to-headers-filter tls_context.mutable_common_tls_context()->mutable_validation_context(); validation_context->mutable_trusted_ca()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem")); - cluster_.mutable_transport_socket()->set_name("envoy.transport_sockets.tls"); - cluster_.mutable_transport_socket()->mutable_typed_config()->PackFrom(tls_context); + if (upstreamProtocol() != Http::CodecType::HTTP3) { + cluster_.mutable_transport_socket()->set_name("envoy.transport_sockets.tls"); + cluster_.mutable_transport_socket()->mutable_typed_config()->PackFrom(tls_context); + } else { + envoy::extensions::transport_sockets::quic::v3::QuicUpstreamTransport quic_context; + quic_context.mutable_upstream_tls_context()->CopyFrom(tls_context); + cluster_.mutable_transport_socket()->set_name("envoy.transport_sockets.quic"); + cluster_.mutable_transport_socket()->mutable_typed_config()->PackFrom(quic_context); + } } const std::string cluster_type_config_use_sub_cluster = fmt::format( @@ -168,8 +178,7 @@ name: envoy.clusters.dynamic_forward_proxy ->mutable_max_pending_requests() ->set_value(max_pending_requests); - // Load the CDS cluster and wait for it to initialize. - cds_helper_.setCds({cluster_}); + // CDS cluster is loaded in createUpstreams() to handle late H3 information. HttpIntegrationTest::initialize(); test_server_->waitForCounterEq("cluster_manager.cluster_added", 1); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); @@ -179,7 +188,7 @@ name: envoy.clusters.dynamic_forward_proxy } void createUpstreams() override { - if (upstream_tls_) { + if (upstream_tls_ && !upstream_cert_name_.empty()) { addFakeUpstream(Ssl::createFakeUpstreamSslContext(upstream_cert_name_, context_manager_, factory_context_), upstreamProtocol(), /*autonomous_upstream=*/false); @@ -200,6 +209,20 @@ name: envoy.clusters.dynamic_forward_proxy cache_file_value_contents_.length(), "\n", cache_file_value_contents_)); } + if (upstreamProtocol() == Http::CodecType::HTTP3) { + protocol_options_.mutable_auto_config()->mutable_http3_protocol_options(); + auto* alt_cache_options = + protocol_options_.mutable_auto_config()->mutable_alternate_protocols_cache_options(); + alt_cache_options->set_name("default_alternate_protocols_cache"); + + alt_cache_options->add_canonical_suffixes(".lyft.com"); + auto* entry = alt_cache_options->add_prepopulated_entries(); + entry->set_hostname("sni.lyft.com"); + entry->set_port(fake_upstreams_[0]->localAddress()->ip()->port()); + ConfigHelper::setProtocolOptions(cluster_, protocol_options_); + } + // Load the CDS cluster and wait for it to initialize. + cds_helper_.setCds({cluster_}); } void testConnectionTiming(IntegrationStreamDecoderPtr& response, bool cached_dns, @@ -313,6 +336,7 @@ name: envoy.clusters.dynamic_forward_proxy std::string upstream_cert_name_{"upstreamlocalhost"}; CdsHelper cds_helper_; envoy::config::cluster::v3::Cluster cluster_; + ConfigHelper::HttpProtocolOptions protocol_options_; std::string cache_file_value_contents_; bool use_cache_file_{}; uint32_t host_ttl_{1}; @@ -957,6 +981,39 @@ TEST_P(ProxyFilterIntegrationTest, UseCacheFileAndTestHappyEyeballs) { EXPECT_EQ(1, test_server_->counter("dns_cache.foo.host_added")->value()); } +#if defined(ENVOY_ENABLE_QUIC) +TEST_P(ProxyFilterIntegrationTest, UseCacheFileAndHttp3) { + upstream_cert_name_ = ""; // Force standard TLS + dns_hostname_ = "sni.lyft.com"; + autonomous_upstream_ = true; + setUpstreamProtocol(Http::CodecType::HTTP3); + + use_cache_file_ = true; + // Unlike TCP happy eyeballs, HTTP/3 will only work if address families differ. + // Prepend a bad address with opposite address family. + if (GetParam() == Network::Address::IpVersion::v6) { + cache_file_value_contents_ = "99.99.99.99:1|1000000|0\n"; + } else { + cache_file_value_contents_ = "[::99]:1|1000000|0\n"; + } + + initializeWithArgs(); + codec_client_ = makeHttpConnection(lookupPort("http")); + std::string host = + fmt::format("sni.lyft.com:{}", fake_upstreams_[0]->localAddress()->ip()->port()); + const Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", host}}; + + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + + // Wait for the request to be received. + test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_total", 1); + EXPECT_TRUE(response->waitForEndStream()); + EXPECT_EQ(1, test_server_->counter("dns_cache.foo.cache_load")->value()); + EXPECT_EQ(1, test_server_->counter("dns_cache.foo.host_added")->value()); +} +#endif + TEST_P(ProxyFilterIntegrationTest, MultipleRequestsLowStreamLimit) { upstream_tls_ = false; // config below uses bootstrap, tls config is in cluster_ // Ensure we only have one connection upstream, one request active at a time. From dbe8cca3787cc0e15f4c3b8d9bed9ab17816e5e3 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 2 Jul 2024 10:28:45 -0700 Subject: [PATCH 26/31] quic: Add a DataSource option for server preferred address config (#34946) This allows for cases when the control plane does not know the correct configuration for the server preferred address, but the needed addresses are available in the context Envoy is running in. Signed-off-by: Greg Greenway --- .../v3/datasource.proto | 53 ++++++ ...ixed_server_preferred_address_config.proto | 2 +- changelogs/current.yaml | 5 + source/common/quic/BUILD | 1 + source/extensions/extensions_build_config.bzl | 1 + source/extensions/extensions_metadata.yaml | 7 + .../quic/server_preferred_address/BUILD | 44 +++++ ...asource_server_preferred_address_config.cc | 112 +++++++++++++ ...tasource_server_preferred_address_config.h | 34 ++++ .../fixed_server_preferred_address_config.cc | 31 +--- .../fixed_server_preferred_address_config.h | 18 -- .../server_preferred_address.cc | 29 ++++ .../server_preferred_address.h | 27 +++ .../quic/server_preferred_address/BUILD | 12 ++ ...atasource_server_preferred_address_test.cc | 156 ++++++++++++++++++ tools/code_format/config.yaml | 1 + 16 files changed, 489 insertions(+), 44 deletions(-) create mode 100644 api/envoy/extensions/quic/server_preferred_address/v3/datasource.proto create mode 100644 source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.cc create mode 100644 source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.h create mode 100644 source/extensions/quic/server_preferred_address/server_preferred_address.cc create mode 100644 source/extensions/quic/server_preferred_address/server_preferred_address.h create mode 100644 test/extensions/quic/server_preferred_address/datasource_server_preferred_address_test.cc diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/datasource.proto b/api/envoy/extensions/quic/server_preferred_address/v3/datasource.proto new file mode 100644 index 000000000000..6baf39850763 --- /dev/null +++ b/api/envoy/extensions/quic/server_preferred_address/v3/datasource.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +package envoy.extensions.quic.server_preferred_address.v3; + +import "envoy/config/core/v3/base.proto"; + +import "xds/annotations/v3/status.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.quic.server_preferred_address.v3"; +option java_outer_classname = "DatasourceProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/quic/server_preferred_address/v3;server_preferred_addressv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: QUIC DataSource server preferred address config] +// [#extension: envoy.quic.server_preferred_address.datasource] + +// Configuration for DataSourceServerPreferredAddressConfig. +message DataSourceServerPreferredAddressConfig { + // [#comment:TODO(danzh2010): discuss with API shepherds before removing WiP status.] + + option (xds.annotations.v3.message_status).work_in_progress = true; + + // Addresses for server preferred address for a single address family (IPv4 or IPv6). + message AddressFamilyConfig { + // The server preferred address sent to clients. The data must contain an IP address string. + config.core.v3.DataSource address = 1 [(validate.rules).message = {required: true}]; + + // The server preferred address port sent to clients. The data must contain a integer port value. + // + // If this is not specified, the listener's port is used. + // + // Note: Envoy currently must receive all packets for a QUIC connection on the same port, so unless + // :ref:`dnat_address ` + // is configured, this must be left unset. + config.core.v3.DataSource port = 2; + + // If there is a DNAT between the client and Envoy, the address that Envoy will observe + // server preferred address packets being sent to. If this is not specified, it is assumed + // there is no DNAT and the server preferred address packets will be sent to the address advertised + // to clients for server preferred address. + config.core.v3.DataSource dnat_address = 3; + } + + // The IPv4 address to advertise to clients for Server Preferred Address. + AddressFamilyConfig ipv4_config = 1; + + // The IPv6 address to advertise to clients for Server Preferred Address. + AddressFamilyConfig ipv6_config = 2; +} diff --git a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto index 28d6cd57873f..20ec9a2a379e 100644 --- a/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto +++ b/api/envoy/extensions/quic/server_preferred_address/v3/fixed_server_preferred_address_config.proto @@ -14,7 +14,7 @@ option java_multiple_files = true; option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/quic/server_preferred_address/v3;server_preferred_addressv3"; option (udpa.annotations.file_status).package_version_status = ACTIVE; -// [#protodoc-title: QUIC server preferred address config] +// [#protodoc-title: QUIC fixed server preferred address config] // [#extension: envoy.quic.server_preferred_address.fixed] // Configuration for FixedServerPreferredAddressConfig. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index d7a32fc14193..354baa656eab 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -434,6 +434,11 @@ new_features: Added :ref:`strip_failure_response ` to allow stripping the failure response details from the JWT authentication filter. +- area: quic + change: | + Added :ref:`DataSourceServerPreferredAddressConfig + ` for cases when + the control plane does not know the correct configuration for the server preferred address. - area: tls change: | added support to match against ``OtherName`` SAN Type under :ref:`match_typed_subject_alt_names diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index ee214a394c73..6e2d8125be4b 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -661,6 +661,7 @@ envoy_cc_library( deps = [ "//envoy/config:typed_config_interface", "//envoy/network:address_interface", + "//envoy/server:factory_context_interface", "@com_github_google_quiche//:quic_platform_socket_address", ], ) diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 20999e45506d..1dc00bb9110c 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -414,6 +414,7 @@ EXTENSIONS = { "envoy.quic.crypto_stream.server.quiche": "//source/extensions/quic/crypto_stream:envoy_quic_default_crypto_server_stream", "envoy.quic.proof_source.filter_chain": "//source/extensions/quic/proof_source:envoy_quic_default_proof_source", "envoy.quic.server_preferred_address.fixed": "//source/extensions/quic/server_preferred_address:fixed_server_preferred_address_config_factory_config", + "envoy.quic.server_preferred_address.datasource": "//source/extensions/quic/server_preferred_address:datasource_server_preferred_address_config_factory_config", "envoy.quic.connection_debug_visitor.basic": "//source/extensions/quic/connection_debug_visitor:envoy_quic_connection_debug_visitor_basic", # diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index a0c4df67d35e..24f5aa605f81 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1014,6 +1014,13 @@ envoy.quic.server_preferred_address.fixed: status: alpha type_urls: - envoy.extensions.quic.server_preferred_address.v3.FixedServerPreferredAddressConfig +envoy.quic.server_preferred_address.datasource: + categories: + - envoy.quic.server_preferred_address + security_posture: robust_to_untrusted_downstream + status: alpha + type_urls: + - envoy.extensions.quic.server_preferred_address.v3.DataSourceServerPreferredAddressConfig envoy.udp_packet_writer.default: categories: - envoy.udp_packet_writer diff --git a/source/extensions/quic/server_preferred_address/BUILD b/source/extensions/quic/server_preferred_address/BUILD index 3e42f9ac7f9d..30d724516efc 100644 --- a/source/extensions/quic/server_preferred_address/BUILD +++ b/source/extensions/quic/server_preferred_address/BUILD @@ -15,12 +15,23 @@ licenses(["notice"]) # Apache 2 envoy_extension_package() +envoy_cc_library( + name = "server_preferred_address_lib", + srcs = ["server_preferred_address.cc"], + hdrs = ["server_preferred_address.h"], + tags = ["nofips"], + deps = [ + "//source/common/quic:envoy_quic_server_preferred_address_config_factory_interface", + ], +) + envoy_cc_library( name = "fixed_server_preferred_address_config_lib", srcs = ["fixed_server_preferred_address_config.cc"], hdrs = ["fixed_server_preferred_address_config.h"], tags = ["nofips"], deps = [ + ":server_preferred_address_lib", "//envoy/registry", "//source/common/quic:envoy_quic_server_preferred_address_config_factory_interface", "//source/common/quic:envoy_quic_utils_lib", @@ -45,3 +56,36 @@ envoy_cc_extension( }, ), ) + +envoy_cc_library( + name = "datasource_server_preferred_address_config_lib", + srcs = ["datasource_server_preferred_address_config.cc"], + hdrs = ["datasource_server_preferred_address_config.h"], + tags = ["nofips"], + deps = [ + ":server_preferred_address_lib", + "//envoy/registry", + "//source/common/config:datasource_lib", + "//source/common/quic:envoy_quic_server_preferred_address_config_factory_interface", + "//source/common/quic:envoy_quic_utils_lib", + "@envoy_api//envoy/extensions/quic/server_preferred_address/v3:pkg_cc_proto", + ], + alwayslink = LEGACY_ALWAYSLINK, +) + +envoy_cc_extension( + name = "datasource_server_preferred_address_config_factory_config", + extra_visibility = [ + "//test:__subpackages__", + ], + tags = ["nofips"], + deps = select( + { + "//bazel:boringssl_fips": [], + "//bazel:boringssl_disabled": [], + "//conditions:default": [ + ":datasource_server_preferred_address_config_lib", + ], + }, + ), +) diff --git a/source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.cc b/source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.cc new file mode 100644 index 000000000000..5f5884221bb7 --- /dev/null +++ b/source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.cc @@ -0,0 +1,112 @@ +#include "source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.h" + +#include "envoy/common/exception.h" + +#include "source/common/common/utility.h" +#include "source/common/config/datasource.h" +#include "source/common/network/utility.h" +#include "source/common/quic/envoy_quic_utils.h" +#include "source/extensions/quic/server_preferred_address/server_preferred_address.h" + +namespace Envoy { +namespace Quic { + +namespace { + +quic::QuicIpAddress parseIp(const envoy::config::core::v3::DataSource& source, + quiche::IpAddressFamily address_family, + absl::string_view address_family_str, const Protobuf::Message& message, + Server::Configuration::ServerFactoryContext& context) { + std::string data = + THROW_OR_RETURN_VALUE(Config::DataSource::read(source, false, context.api()), std::string); + + quic::QuicIpAddress ip; + if (!ip.FromString(std::string(StringUtil::trim(data)))) { + ProtoExceptionUtil::throwProtoValidationException( + absl::StrCat("bad ", address_family_str, " server preferred address: ", data), message); + } + + if (ip.address_family() != address_family) { + ProtoExceptionUtil::throwProtoValidationException( + absl::StrCat("wrong address family for ", address_family_str, + " server preferred address: ", data), + message); + } + return ip; +} + +ServerPreferredAddressConfig::IpVersionConfig +parseFamily(const envoy::extensions::quic::server_preferred_address::v3:: + DataSourceServerPreferredAddressConfig::AddressFamilyConfig& addresses, + quiche::IpAddressFamily address_family, absl::string_view address_family_str, + const Protobuf::Message& message, + Server::Configuration::ServerFactoryContext& context) { + ServerPreferredAddressConfig::IpVersionConfig ret; + + const quic::QuicIpAddress spa_addr = + parseIp(addresses.address(), address_family, address_family_str, message, context); + + if (!addresses.has_dnat_address() && addresses.has_port()) { + ProtoExceptionUtil::throwProtoValidationException( + fmt::format("port must be unset unless 'dnat_address' is set " + "for address family {}", + address_family_str), + message); + } + + uint16_t spa_port = 0; + if (addresses.has_port()) { + std::string port_str = THROW_OR_RETURN_VALUE( + Config::DataSource::read(addresses.port(), false, context.api()), std::string); + + // absl::SimpleAtoi doesn't work with uint16_t, so first convert to uint32_t. + uint32_t big_port = 0; + const bool success = absl::SimpleAtoi(port_str, &big_port); + if (!success || big_port > UINT16_MAX) { + ProtoExceptionUtil::throwProtoValidationException( + absl::StrCat("server preferred address ", address_family_str, + " port was not a valid port: ", port_str), + message); + } + + spa_port = big_port; + } + ret.spa_ = quic::QuicSocketAddress(spa_addr, spa_port); + + if (addresses.has_dnat_address()) { + ret.dnat_ = + parseIp(addresses.dnat_address(), address_family, address_family_str, message, context); + } + + return ret; +} + +} // namespace + +Quic::EnvoyQuicServerPreferredAddressConfigPtr +DataSourceServerPreferredAddressConfigFactory::createServerPreferredAddressConfig( + const Protobuf::Message& message, ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::ServerFactoryContext& context) { + auto& config = + MessageUtil::downcastAndValidate( + message, validation_visitor); + + ServerPreferredAddressConfig::IpVersionConfig v4; + if (config.has_ipv4_config()) { + v4 = parseFamily(config.ipv4_config(), quiche::IpAddressFamily::IP_V4, "v4", message, context); + } + + ServerPreferredAddressConfig::IpVersionConfig v6; + if (config.has_ipv6_config()) { + v6 = parseFamily(config.ipv6_config(), quiche::IpAddressFamily::IP_V6, "v6", message, context); + } + + return std::make_unique(v4, v6); +} + +REGISTER_FACTORY(DataSourceServerPreferredAddressConfigFactory, + Quic::EnvoyQuicServerPreferredAddressConfigFactory); + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.h b/source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.h new file mode 100644 index 000000000000..725c96e60e0a --- /dev/null +++ b/source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.h @@ -0,0 +1,34 @@ +#pragma once + +#include "envoy/extensions/quic/server_preferred_address/v3/datasource.pb.h" +#include "envoy/extensions/quic/server_preferred_address/v3/datasource.pb.validate.h" +#include "envoy/registry/registry.h" + +#include "source/common/quic/envoy_quic_server_preferred_address_config_factory.h" + +namespace Envoy { +namespace Quic { + +// This method of configuring server preferred address allows fetching the config from +// a `DataSource`, for situations where the control plane doesn't know the correct value +// but it is available in the context in which Envoy is running. +class DataSourceServerPreferredAddressConfigFactory + : public Envoy::Quic::EnvoyQuicServerPreferredAddressConfigFactory { +public: + std::string name() const override { return "quic.server_preferred_address.datasource"; } + + Envoy::Quic::EnvoyQuicServerPreferredAddressConfigPtr + createServerPreferredAddressConfig(const Protobuf::Message& message, + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::ServerFactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return ProtobufTypes::MessagePtr{new envoy::extensions::quic::server_preferred_address::v3:: + DataSourceServerPreferredAddressConfig()}; + } +}; + +DECLARE_FACTORY(DataSourceServerPreferredAddressConfigFactory); + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc index b4afcb66d704..e77cefd5bff2 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.cc @@ -2,20 +2,13 @@ #include "source/common/network/utility.h" #include "source/common/quic/envoy_quic_utils.h" +#include "source/extensions/quic/server_preferred_address/server_preferred_address.h" namespace Envoy { namespace Quic { namespace { -quic::QuicSocketAddress ipOrAddressToAddress(const quic::QuicSocketAddress& address, int32_t port) { - if (address.port() == 0) { - return quic::QuicSocketAddress(address.host(), port); - } - - return address; -} - quic::QuicIpAddress parseIp(const std::string& addr, absl::string_view address_family, const Protobuf::Message& message) { quic::QuicIpAddress ip; @@ -65,13 +58,13 @@ parseIpAddressFromSocketAddress(const envoy::config::core::v3::SocketAddress& ad return socket_addr.host(); } -FixedServerPreferredAddressConfig::FamilyAddresses +ServerPreferredAddressConfig::IpVersionConfig parseFamily(const std::string& addr_string, const envoy::extensions::quic::server_preferred_address::v3:: FixedServerPreferredAddressConfig::AddressFamilyConfig* addresses, Network::Address::IpVersion version, absl::string_view address_family, const Protobuf::Message& message) { - FixedServerPreferredAddressConfig::FamilyAddresses ret; + ServerPreferredAddressConfig::IpVersionConfig ret; if (addresses != nullptr) { if (addresses->has_dnat_address() && !addresses->has_address()) { ProtoExceptionUtil::throwProtoValidationException( @@ -107,18 +100,6 @@ parseFamily(const std::string& addr_string, } // namespace -EnvoyQuicServerPreferredAddressConfig::Addresses -FixedServerPreferredAddressConfig::getServerPreferredAddresses( - const Network::Address::InstanceConstSharedPtr& local_address) { - int32_t port = local_address->ip()->port(); - Addresses addresses; - addresses.ipv4_ = ipOrAddressToAddress(v4_.spa_, port); - addresses.ipv6_ = ipOrAddressToAddress(v6_.spa_, port); - addresses.dnat_ipv4_ = quic::QuicSocketAddress(v4_.dnat_, port); - addresses.dnat_ipv6_ = quic::QuicSocketAddress(v6_.dnat_, port); - return addresses; -} - Quic::EnvoyQuicServerPreferredAddressConfigPtr FixedServerPreferredAddressConfigFactory::createServerPreferredAddressConfig( const Protobuf::Message& message, ProtobufMessage::ValidationVisitor& validation_visitor, @@ -128,14 +109,14 @@ FixedServerPreferredAddressConfigFactory::createServerPreferredAddressConfig( FixedServerPreferredAddressConfig&>(message, validation_visitor); - FixedServerPreferredAddressConfig::FamilyAddresses v4 = + ServerPreferredAddressConfig::IpVersionConfig v4 = parseFamily(config.ipv4_address(), config.has_ipv4_config() ? &config.ipv4_config() : nullptr, Network::Address::IpVersion::v4, "v4", message); - FixedServerPreferredAddressConfig::FamilyAddresses v6 = + ServerPreferredAddressConfig::IpVersionConfig v6 = parseFamily(config.ipv6_address(), config.has_ipv6_config() ? &config.ipv6_config() : nullptr, Network::Address::IpVersion::v6, "v6", message); - return std::make_unique(v4, v6); + return std::make_unique(v4, v6); } REGISTER_FACTORY(FixedServerPreferredAddressConfigFactory, diff --git a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h index e93b470734c1..9cfb8f7139c9 100644 --- a/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h +++ b/source/extensions/quic/server_preferred_address/fixed_server_preferred_address_config.h @@ -9,24 +9,6 @@ namespace Envoy { namespace Quic { -class FixedServerPreferredAddressConfig : public Quic::EnvoyQuicServerPreferredAddressConfig { -public: - struct FamilyAddresses { - quic::QuicSocketAddress spa_; - quic::QuicIpAddress dnat_; - }; - - FixedServerPreferredAddressConfig(const FamilyAddresses& v4, const FamilyAddresses& v6) - : v4_(v4), v6_(v6) {} - - Addresses getServerPreferredAddresses( - const Network::Address::InstanceConstSharedPtr& local_address) override; - -private: - const FamilyAddresses v4_; - const FamilyAddresses v6_; -}; - class FixedServerPreferredAddressConfigFactory : public Quic::EnvoyQuicServerPreferredAddressConfigFactory { public: diff --git a/source/extensions/quic/server_preferred_address/server_preferred_address.cc b/source/extensions/quic/server_preferred_address/server_preferred_address.cc new file mode 100644 index 000000000000..bd2ddf1e15e5 --- /dev/null +++ b/source/extensions/quic/server_preferred_address/server_preferred_address.cc @@ -0,0 +1,29 @@ +#include "source/extensions/quic/server_preferred_address/server_preferred_address.h" + +namespace Envoy { +namespace Quic { + +namespace { +quic::QuicSocketAddress ipOrAddressToAddress(const quic::QuicSocketAddress& address, int32_t port) { + if (address.port() == 0) { + return quic::QuicSocketAddress(address.host(), port); + } + + return address; +} +} // namespace + +EnvoyQuicServerPreferredAddressConfig::Addresses +ServerPreferredAddressConfig::getServerPreferredAddresses( + const Network::Address::InstanceConstSharedPtr& local_address) { + int32_t port = local_address->ip()->port(); + Addresses addresses; + addresses.ipv4_ = ipOrAddressToAddress(v4_.spa_, port); + addresses.ipv6_ = ipOrAddressToAddress(v6_.spa_, port); + addresses.dnat_ipv4_ = quic::QuicSocketAddress(v4_.dnat_, port); + addresses.dnat_ipv6_ = quic::QuicSocketAddress(v6_.dnat_, port); + return addresses; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic/server_preferred_address/server_preferred_address.h b/source/extensions/quic/server_preferred_address/server_preferred_address.h new file mode 100644 index 000000000000..0889a972e03d --- /dev/null +++ b/source/extensions/quic/server_preferred_address/server_preferred_address.h @@ -0,0 +1,27 @@ +#pragma once + +#include "source/common/quic/envoy_quic_server_preferred_address_config_factory.h" + +namespace Envoy { +namespace Quic { + +class ServerPreferredAddressConfig : public Quic::EnvoyQuicServerPreferredAddressConfig { +public: + struct IpVersionConfig { + quic::QuicSocketAddress spa_; + quic::QuicIpAddress dnat_; + }; + + ServerPreferredAddressConfig(const IpVersionConfig& v4, const IpVersionConfig& v6) + : v4_(v4), v6_(v6) {} + + Addresses getServerPreferredAddresses( + const Network::Address::InstanceConstSharedPtr& local_address) override; + +private: + const IpVersionConfig v4_; + const IpVersionConfig v6_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic/server_preferred_address/BUILD b/test/extensions/quic/server_preferred_address/BUILD index 5ae655ec0ce0..32daabc31694 100644 --- a/test/extensions/quic/server_preferred_address/BUILD +++ b/test/extensions/quic/server_preferred_address/BUILD @@ -11,6 +11,18 @@ licenses(["notice"]) # Apache 2 envoy_package() +envoy_extension_cc_test( + name = "datasource_server_preferred_address_test", + srcs = ["datasource_server_preferred_address_test.cc"], + extension_names = ["envoy.quic.server_preferred_address.datasource"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic/server_preferred_address:datasource_server_preferred_address_config_lib", + "//test/mocks/protobuf:protobuf_mocks", + "//test/mocks/server:server_factory_context_mocks", + ], +) + envoy_extension_cc_test( name = "fixed_server_preferred_address_test", srcs = ["fixed_server_preferred_address_test.cc"], diff --git a/test/extensions/quic/server_preferred_address/datasource_server_preferred_address_test.cc b/test/extensions/quic/server_preferred_address/datasource_server_preferred_address_test.cc new file mode 100644 index 000000000000..30133fd4bc3e --- /dev/null +++ b/test/extensions/quic/server_preferred_address/datasource_server_preferred_address_test.cc @@ -0,0 +1,156 @@ +#include "source/common/network/utility.h" +#include "source/extensions/quic/server_preferred_address/datasource_server_preferred_address_config.h" + +#include "test/mocks/protobuf/mocks.h" +#include "test/mocks/server/server_factory_context.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Quic { + +class DataSourceServerPreferredAddressConfigTest : public ::testing::Test { +public: + DataSourceServerPreferredAddressConfigFactory factory_; + testing::NiceMock visitor_; + testing::NiceMock context_; +}; + +TEST_F(DataSourceServerPreferredAddressConfigTest, Validation) { + { + // Bad address. + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig + cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string("not an address"); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, context_), + EnvoyException, ".*bad v4 server preferred address: not an address.*"); + } + { + // Bad port. + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig + cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_port()->set_inline_string("1000000"); // Out of range. + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_inline_string("127.0.0.1"); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, context_), + EnvoyException, + ".*server preferred address v4 port was not a valid port: 1000000.*"); + } + { + // Cannot set dnat address but not spa address. + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig + cfg; + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_inline_string("127.0.0.1"); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, context_), + EnvoyException, + ".*AddressFamilyConfigValidationError.Address: value is required.*"); + } + { + // Cannot set port on address if dnat address isn't set. + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig + cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_port()->set_inline_string("1"); + EXPECT_THROW_WITH_REGEX( + factory_.createServerPreferredAddressConfig(cfg, visitor_, context_), EnvoyException, + ".*port must be unset unless 'dnat_address' is set for address family v4.*"); + } + { + // v6 address in v4 field. + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig + cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string("::1"); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, context_), + EnvoyException, + ".*wrong address family for v4 server preferred address.*"); + } + { + // v4 address in v6 field. + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig + cfg; + cfg.mutable_ipv6_config()->mutable_address()->set_inline_string("127.0.0.1"); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, context_), + EnvoyException, + ".*wrong address family for v6 server preferred address.*"); + } + + // Cannot read address. + { + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig + cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_environment_variable( + "does_not_exist_env_var"); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, context_), + EnvoyException, ".*Environment variable doesn't exist.*"); + } + + // Cannot read port. + { + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig + cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_port()->set_environment_variable("does_not_exist_env_var"); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_inline_string("127.0.0.1"); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, context_), + EnvoyException, ".*Environment variable doesn't exist.*"); + } + + // Cannot read dnat_address. + { + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig + cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string("127.0.0.1"); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_environment_variable( + "does_not_exist_env_var"); + EXPECT_THROW_WITH_REGEX(factory_.createServerPreferredAddressConfig(cfg, visitor_, context_), + EnvoyException, ".*Environment variable doesn't exist.*"); + } +} + +TEST_F(DataSourceServerPreferredAddressConfigTest, AddressAndPortIgnoresListenerPort) { + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string("1.2.3.4"); + cfg.mutable_ipv4_config()->mutable_port()->set_inline_string("5"); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_inline_string("127.0.0.1"); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, context_); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:5"); +} + +TEST_F(DataSourceServerPreferredAddressConfigTest, AddressAndUnsetPortUsesListenerPort) { + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string("1.2.3.4"); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, context_); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:1234"); +} + +TEST_F(DataSourceServerPreferredAddressConfigTest, DnatAddressAndUnsetPortUsesListenerPort) { + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string("1.2.3.4"); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_inline_string("127.0.0.1"); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, context_); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.dnat_ipv4_.ToString(), "127.0.0.1:1234"); +} + +// Oftentimes files have a newline at the end, so accept data with whitespace around it. +TEST_F(DataSourceServerPreferredAddressConfigTest, NewlinesAccepted) { + envoy::extensions::quic::server_preferred_address::v3::DataSourceServerPreferredAddressConfig cfg; + cfg.mutable_ipv4_config()->mutable_address()->set_inline_string(" 1.2.3.4\n"); + cfg.mutable_ipv4_config()->mutable_port()->set_inline_string(" 443\n"); + cfg.mutable_ipv4_config()->mutable_dnat_address()->set_inline_string(" 127.0.0.1\n"); + auto obj = factory_.createServerPreferredAddressConfig(cfg, visitor_, context_); + auto addresses = obj->getServerPreferredAddresses( + Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 1234)); + EXPECT_EQ(addresses.ipv4_.ToString(), "1.2.3.4:443"); + EXPECT_EQ(addresses.dnat_ipv4_.ToString(), "127.0.0.1:1234"); +} + +} // namespace Quic +} // namespace Envoy diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 67e1aa75e783..4f3846c233be 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -185,6 +185,7 @@ paths: - source/extensions/matching - source/extensions/network - source/extensions/path + - source/extensions/quic/server_preferred_address - source/extensions/rate_limit_descriptors - source/extensions/resource_monitors - source/extensions/retry From 067ba355b6ce8948a69a69bfc9f5ba82e9a4da28 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 2 Jul 2024 14:27:11 -0400 Subject: [PATCH 27/31] mobile ci: removing the iOS no-http3 build (#35019) Signed-off-by: Alyssa Wilk --- .../workflows/mobile-compile_time_options.yml | 32 ------------------- mobile/.bazelrc | 1 + 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/.github/workflows/mobile-compile_time_options.yml b/.github/workflows/mobile-compile_time_options.yml index a94335618cc0..64770d4458af 100644 --- a/.github/workflows/mobile-compile_time_options.yml +++ b/.github/workflows/mobile-compile_time_options.yml @@ -69,38 +69,6 @@ jobs: --config=mobile-remote-ci-cc-full-protos-enabled //test/common/... //test/cc/... - build: - permissions: - contents: read - packages: read - uses: ./.github/workflows/_run.yml - if: ${{ fromJSON(needs.load.outputs.request).run.mobile-compile-time-options }} - needs: load - with: - args: ${{ matrix.args }} - command: ./bazelw - container-command: - request: ${{ needs.load.outputs.request }} - runs-on: macos-12 - source: ${{ matrix.source }} - steps-pre: ${{ matrix.steps-pre }} - target: ${{ matrix.target || matrix.name }} - trusted: ${{ fromJSON(needs.load.outputs.trusted) }} - timeout-minutes: 120 - working-directory: mobile - strategy: - fail-fast: false - matrix: - include: - - name: swift-build - args: >- - build - --config=mobile-remote-ci-macos-swift - //library/swift:ios_framework - source: | - source ./ci/mac_ci_setup.sh - ./bazelw shutdown - request: secrets: app-id: ${{ secrets.ENVOY_CI_APP_ID }} diff --git a/mobile/.bazelrc b/mobile/.bazelrc index e8efba686abb..d3bb3c213d8e 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -265,6 +265,7 @@ test:mobile-remote-ci-cc-full-protos-enabled --define=envoy_full_protos=enabled build:mobile-remote-ci-macos-kotlin --config=mobile-remote-ci-macos build:mobile-remote-ci-macos-kotlin --fat_apk_cpu=x86_64 +# TODO(alyssar) remove in a follow-up PR build:mobile-remote-ci-macos-swift --config=mobile-remote-ci-macos build:mobile-remote-ci-macos-swift --config=mobile-test-ios build:mobile-remote-ci-macos-swift --@envoy//bazel:http3=False From e74bbb0925afc3efd7564b2de5f8bbe2b5708c36 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 2 Jul 2024 14:29:18 -0400 Subject: [PATCH 28/31] mobile: remove unused APIs (#35017) Signed-off-by: Alyssa Wilk --- .../workflows/mobile-compile_time_options.yml | 6 - .github/workflows/mobile-release.yml | 8 - mobile/library/cc/engine_builder.cc | 135 +----------- mobile/library/cc/engine_builder.h | 114 +--------- .../engine/EnvoyConfiguration.java | 59 +---- .../envoymobile/engine/JniLibrary.java | 6 +- .../impl/NativeCronvoyEngineBuilderImpl.java | 12 +- mobile/library/jni/jni_impl.cc | 58 +---- .../kotlin/io/envoyproxy/envoymobile/BUILD | 22 -- .../envoyproxy/envoymobile/EngineBuilder.kt | 172 --------------- .../library/objective-c/EnvoyConfiguration.h | 29 +-- .../library/objective-c/EnvoyConfiguration.mm | 59 +---- mobile/library/swift/EngineBuilder.swift | 204 +----------------- mobile/test/cc/unit/envoy_config_test.cc | 136 ------------ mobile/test/common/integration/BUILD | 121 ----------- .../integration/cds_integration_test.cc | 128 ----------- .../integration/rtds_integration_test.cc | 115 ---------- .../integration/sds_integration_test.cc | 109 ---------- .../integration/xds_integration_test.cc | 102 --------- .../common/integration/xds_integration_test.h | 51 ----- .../common/integration/xds_test_server.cc | 112 ---------- .../test/common/integration/xds_test_server.h | 56 ----- .../engine/EnvoyConfigurationTest.kt | 129 ----------- .../envoymobile/engine/testing/BUILD | 16 -- .../engine/testing/XdsTestServerFactory.java | 42 ---- mobile/test/jni/BUILD | 34 --- .../test/jni/jni_xds_test_server_factory.cc | 81 ------- mobile/test/kotlin/integration/BUILD | 22 -- .../test/kotlin/integration/EngineApiTest.kt | 31 --- mobile/test/kotlin/integration/XdsTest.kt | 68 ------ .../envoymobile/EngineBuilderTest.kt | 25 --- mobile/test/non_hermetic/BUILD | 47 ---- .../gcp_traffic_director_integration_test.cc | 139 ------------ mobile/test/swift/EngineBuilderTests.swift | 85 -------- 34 files changed, 13 insertions(+), 2520 deletions(-) delete mode 100644 mobile/test/common/integration/cds_integration_test.cc delete mode 100644 mobile/test/common/integration/rtds_integration_test.cc delete mode 100644 mobile/test/common/integration/sds_integration_test.cc delete mode 100644 mobile/test/common/integration/xds_integration_test.cc delete mode 100644 mobile/test/common/integration/xds_integration_test.h delete mode 100644 mobile/test/common/integration/xds_test_server.cc delete mode 100644 mobile/test/common/integration/xds_test_server.h delete mode 100644 mobile/test/java/io/envoyproxy/envoymobile/engine/testing/XdsTestServerFactory.java delete mode 100644 mobile/test/jni/jni_xds_test_server_factory.cc delete mode 100644 mobile/test/kotlin/integration/XdsTest.kt delete mode 100644 mobile/test/non_hermetic/BUILD delete mode 100644 mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc diff --git a/.github/workflows/mobile-compile_time_options.yml b/.github/workflows/mobile-compile_time_options.yml index 64770d4458af..c60d1375bd4c 100644 --- a/.github/workflows/mobile-compile_time_options.yml +++ b/.github/workflows/mobile-compile_time_options.yml @@ -56,12 +56,6 @@ jobs: build --config=mobile-remote-ci-cc-no-exceptions //test/performance:test_binary_size //library/cc/... - - name: Running C++ tests with xDS enabled - target: cc-tests-xds-enabled - args: >- - test - --config=mobile-remote-ci-cc-xds-enabled - //test/common/integration/... - name: Running C++ tests with full protos enabled target: cc-tests-full-protos-enabled args: >- diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index 46f0fbed3521..98256b2aa290 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -72,13 +72,6 @@ jobs: --define=pom_version=$VERSION //:android_dist output: envoy - - target: xds-release - args: >- - build - --config=mobile-remote-release-clang-android-publish-xds - --define=pom_version=$VERSION - //:android_xds_dist - output: envoy_xds deploy: needs: release @@ -92,7 +85,6 @@ jobs: matrix: include: - output: envoy - - output: envoy_xds steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index 4dbd0c9b7cb5..33b7f384cc26 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -41,94 +41,6 @@ namespace Envoy { namespace Platform { -#ifdef ENVOY_MOBILE_XDS -XdsBuilder::XdsBuilder(std::string xds_server_address, const uint32_t xds_server_port) - : xds_server_address_(std::move(xds_server_address)), xds_server_port_(xds_server_port) {} - -XdsBuilder& XdsBuilder::addInitialStreamHeader(std::string header, std::string value) { - envoy::config::core::v3::HeaderValue header_value; - header_value.set_key(std::move(header)); - header_value.set_value(std::move(value)); - xds_initial_grpc_metadata_.emplace_back(std::move(header_value)); - return *this; -} - -XdsBuilder& XdsBuilder::setSslRootCerts(std::string root_certs) { - ssl_root_certs_ = std::move(root_certs); - return *this; -} - -XdsBuilder& XdsBuilder::addRuntimeDiscoveryService(std::string resource_name, - const int timeout_in_seconds) { - rtds_resource_name_ = std::move(resource_name); - rtds_timeout_in_seconds_ = timeout_in_seconds > 0 ? timeout_in_seconds : DefaultXdsTimeout; - return *this; -} - -XdsBuilder& XdsBuilder::addClusterDiscoveryService(std::string cds_resources_locator, - const int timeout_in_seconds) { - enable_cds_ = true; - cds_resources_locator_ = std::move(cds_resources_locator); - cds_timeout_in_seconds_ = timeout_in_seconds > 0 ? timeout_in_seconds : DefaultXdsTimeout; - return *this; -} - -void XdsBuilder::build(envoy::config::bootstrap::v3::Bootstrap& bootstrap) const { - auto* ads_config = bootstrap.mutable_dynamic_resources()->mutable_ads_config(); - ads_config->set_transport_api_version(envoy::config::core::v3::ApiVersion::V3); - ads_config->set_set_node_on_first_message_only(true); - ads_config->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); - - auto& grpc_service = *ads_config->add_grpc_services(); - grpc_service.mutable_envoy_grpc()->set_cluster_name("base"); - grpc_service.mutable_envoy_grpc()->set_authority( - absl::StrCat(xds_server_address_, ":", xds_server_port_)); - - if (!xds_initial_grpc_metadata_.empty()) { - grpc_service.mutable_initial_metadata()->Assign(xds_initial_grpc_metadata_.begin(), - xds_initial_grpc_metadata_.end()); - } - - if (!rtds_resource_name_.empty()) { - auto* layered_runtime = bootstrap.mutable_layered_runtime(); - auto* layer = layered_runtime->add_layers(); - layer->set_name("rtds_layer"); - auto* rtds_layer = layer->mutable_rtds_layer(); - rtds_layer->set_name(rtds_resource_name_); - auto* rtds_config = rtds_layer->mutable_rtds_config(); - rtds_config->mutable_ads(); - rtds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); - rtds_config->mutable_initial_fetch_timeout()->set_seconds(rtds_timeout_in_seconds_); - } - - if (enable_cds_) { - auto* cds_config = bootstrap.mutable_dynamic_resources()->mutable_cds_config(); - if (cds_resources_locator_.empty()) { - cds_config->mutable_ads(); - } else { - bootstrap.mutable_dynamic_resources()->set_cds_resources_locator(cds_resources_locator_); - cds_config->mutable_api_config_source()->set_api_type( - envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC); - cds_config->mutable_api_config_source()->set_transport_api_version( - envoy::config::core::v3::ApiVersion::V3); - } - cds_config->mutable_initial_fetch_timeout()->set_seconds(cds_timeout_in_seconds_); - cds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); - bootstrap.add_node_context_params("cluster"); - // Stat prefixes that we use in tests. - auto* list = - bootstrap.mutable_stats_config()->mutable_stats_matcher()->mutable_inclusion_list(); - list->add_patterns()->set_exact("cluster_manager.active_clusters"); - list->add_patterns()->set_exact("cluster_manager.cluster_added"); - list->add_patterns()->set_exact("cluster_manager.cluster_updated"); - list->add_patterns()->set_exact("cluster_manager.cluster_removed"); - // Allow SDS related stats. - list->add_patterns()->mutable_safe_regex()->set_regex("sds\\..*"); - list->add_patterns()->mutable_safe_regex()->set_regex(".*\\.ssl_context_update_by_sds"); - } -} -#endif - EngineBuilder::EngineBuilder() : callbacks_(std::make_unique()) { #ifndef ENVOY_ENABLE_QUIC enable_http3_ = false; @@ -340,33 +252,6 @@ EngineBuilder& EngineBuilder::enforceTrustChainVerification(bool trust_chain_ver return *this; } -EngineBuilder& EngineBuilder::setNodeId(std::string node_id) { - node_id_ = std::move(node_id); - return *this; -} - -EngineBuilder& EngineBuilder::setNodeLocality(std::string region, std::string zone, - std::string sub_zone) { - node_locality_ = {std::move(region), std::move(zone), std::move(sub_zone)}; - return *this; -} - -EngineBuilder& EngineBuilder::setNodeMetadata(ProtobufWkt::Struct node_metadata) { - node_metadata_ = std::move(node_metadata); - return *this; -} - -#ifdef ENVOY_MOBILE_XDS -EngineBuilder& EngineBuilder::setXds(XdsBuilder xds_builder) { - xds_builder_ = std::move(xds_builder); - // Add the XdsBuilder's xDS server hostname and port to the list of DNS addresses to preresolve in - // the `base` DFP cluster. - dns_preresolve_hostnames_.push_back( - {xds_builder_->xds_server_address_ /* host */, xds_builder_->xds_server_port_ /* port */}); - return *this; -} -#endif - EngineBuilder& EngineBuilder::setUpstreamTlsSni(std::string sni) { upstream_tls_sni_ = std::move(sni); return *this; @@ -638,11 +523,6 @@ std::unique_ptr EngineBuilder::generate validation->mutable_custom_validator_config()->mutable_typed_config()->PackFrom(validator); } else { std::string certs; -#ifdef ENVOY_MOBILE_XDS - if (xds_builder_ && !xds_builder_->ssl_root_certs_.empty()) { - certs = xds_builder_->ssl_root_certs_; - } -#endif if (certs.empty()) { // The xDS builder doesn't supply root certs, so we'll use the certs packed with Envoy Mobile, @@ -857,16 +737,8 @@ std::unique_ptr EngineBuilder::generate // Set up node auto* node = bootstrap->mutable_node(); - node->set_id(node_id_.empty() ? "envoy-mobile" : node_id_); + node->set_id("envoy-mobile"); node->set_cluster("envoy-mobile"); - if (node_locality_ && !node_locality_->region.empty()) { - node->mutable_locality()->set_region(node_locality_->region); - node->mutable_locality()->set_zone(node_locality_->zone); - node->mutable_locality()->set_sub_zone(node_locality_->sub_zone); - } - if (node_metadata_.has_value()) { - *node->mutable_metadata() = *node_metadata_; - } ProtobufWkt::Struct& metadata = *node->mutable_metadata(); (*metadata.mutable_fields())["app_id"].set_string_value(app_id_); (*metadata.mutable_fields())["app_version"].set_string_value(app_version_); @@ -905,11 +777,6 @@ std::unique_ptr EngineBuilder::generate *dns_cache_config->mutable_typed_dns_resolver_config()); bootstrap->mutable_dynamic_resources(); -#ifdef ENVOY_MOBILE_XDS - if (xds_builder_) { - xds_builder_->build(*bootstrap); - } -#endif envoy::config::listener::v3::ApiListenerManager api; auto* listener_manager = bootstrap->mutable_listener_manager(); diff --git a/mobile/library/cc/engine_builder.h b/mobile/library/cc/engine_builder.h index 35be36fbcb7a..3b6e5c055a1d 100644 --- a/mobile/library/cc/engine_builder.h +++ b/mobile/library/cc/engine_builder.h @@ -20,100 +20,6 @@ namespace Envoy { namespace Platform { -constexpr int DefaultXdsTimeout = 5; - -// Forward declaration so it can be referenced by XdsBuilder. -class EngineBuilder; - -// Represents the locality information in the Bootstrap's node, as defined in: -// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#envoy-v3-api-msg-config-core-v3-locality -struct NodeLocality { - std::string region; - std::string zone; - std::string sub_zone; -}; - -#ifdef ENVOY_MOBILE_XDS -// A class for building the xDS configuration for the Envoy Mobile engine. -// xDS is a protocol for dynamic configuration of Envoy instances, more information can be found in: -// https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol. -// -// This class is typically used as input to the EngineBuilder's setXds() method. -class XdsBuilder final { -public: - // `xds_server_address`: the host name or IP address of the xDS management server. The xDS server - // must support the ADS protocol - // (https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#aggregated-xds-ads). - // `xds_server_port`: the port on which the xDS management server listens for ADS discovery - // requests. - XdsBuilder(std::string xds_server_address, const uint32_t xds_server_port); - - // Adds a header to the initial HTTP metadata headers sent on the gRPC stream. - // - // A common use for the initial metadata headers is for authentication to the xDS management - // server. - // - // For example, if using API keys to authenticate to Traffic Director on GCP (see - // https://cloud.google.com/docs/authentication/api-keys for details), invoke: - // builder.addInitialStreamHeader("x-goog-api-key", api_key_token) - // .addInitialStreamHeader("X-Android-Package", app_package_name) - // .addInitialStreamHeader("X-Android-Cert", sha1_key_fingerprint); - XdsBuilder& addInitialStreamHeader(std::string header, std::string value); - - // Sets the PEM-encoded server root certificates used to negotiate the TLS handshake for the gRPC - // connection. If no root certs are specified, the operating system defaults are used. - XdsBuilder& setSslRootCerts(std::string root_certs); - - // Adds Runtime Discovery Service (RTDS) to the Runtime layers of the Bootstrap configuration, - // to retrieve dynamic runtime configuration via the xDS management server. - // - // `resource_name`: The runtime config resource to subscribe to. - // `timeout_in_seconds`: specifies the `initial_fetch_timeout` field on the - // api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch - // timeout value of 5s, to prevent mobile app initialization from stalling. The default - // parameter value may change through the course of experimentation and no assumptions should - // be made of its exact value. - XdsBuilder& addRuntimeDiscoveryService(std::string resource_name, - int timeout_in_seconds = DefaultXdsTimeout); - - // Adds the Cluster Discovery Service (CDS) configuration for retrieving dynamic cluster resources - // via the xDS management server. - // - // `cds_resources_locator`: the xdstp:// URI for subscribing to the cluster resources. - // If not using xdstp, then `cds_resources_locator` should be set to the empty string. - // `timeout_in_seconds`: specifies the `initial_fetch_timeout` field on the - // api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch - // timeout value of 5s, to prevent mobile app initialization from stalling. The default - // parameter value may change through the course of experimentation and no assumptions should - // be made of its exact value. - XdsBuilder& addClusterDiscoveryService(std::string cds_resources_locator = "", - int timeout_in_seconds = DefaultXdsTimeout); - -protected: - // Sets the xDS configuration specified on this XdsBuilder instance on the Bootstrap proto - // provided as an input parameter. - // - // This method takes in a modifiable Bootstrap proto pointer because returning a new Bootstrap - // proto would rely on proto's MergeFrom behavior, which can lead to unexpected results in the - // Bootstrap config. - void build(envoy::config::bootstrap::v3::Bootstrap& bootstrap) const; - -private: - // Required so that EngineBuilder can call the XdsBuilder's protected build() method. - friend class EngineBuilder; - - std::string xds_server_address_; - uint32_t xds_server_port_; - std::vector xds_initial_grpc_metadata_; - std::string ssl_root_certs_; - std::string rtds_resource_name_; - int rtds_timeout_in_seconds_ = DefaultXdsTimeout; - bool enable_cds_ = false; - std::string cds_resources_locator_; - int cds_timeout_in_seconds_ = DefaultXdsTimeout; -}; -#endif - // The C++ Engine builder creates a structured bootstrap proto and modifies it through parameters // set through the EngineBuilder API calls to produce the Bootstrap config that the Engine is // created from. @@ -166,18 +72,7 @@ class EngineBuilder { EngineBuilder& enforceTrustChainVerification(bool trust_chain_verification_on); EngineBuilder& setUpstreamTlsSni(std::string sni); EngineBuilder& enablePlatformCertificatesValidation(bool platform_certificates_validation_on); - // Sets the node.id field in the Bootstrap configuration. - EngineBuilder& setNodeId(std::string node_id); - // Sets the node.locality field in the Bootstrap configuration. - EngineBuilder& setNodeLocality(std::string region, std::string zone, std::string sub_zone); - // Sets the node.metadata field in the Bootstrap configuration. - EngineBuilder& setNodeMetadata(ProtobufWkt::Struct node_metadata); -#ifdef ENVOY_MOBILE_XDS - // Sets the xDS configuration for the Envoy Mobile engine. - // - // `xds_builder`: the XdsBuilder instance used to specify the xDS configuration options. - EngineBuilder& setXds(XdsBuilder xds_builder); -#endif + EngineBuilder& enableDnsCache(bool dns_cache_on, int save_interval_seconds = 1); EngineBuilder& setForceAlwaysUsev6(bool value); // Adds the hostnames that should be pre-resolved by DNS prior to the first request issued for @@ -249,9 +144,6 @@ class EngineBuilder { bool brotli_decompression_filter_ = false; bool socket_tagging_filter_ = false; bool platform_certificates_validation_on_ = false; - std::string node_id_; - absl::optional node_locality_ = absl::nullopt; - absl::optional node_metadata_ = absl::nullopt; bool dns_cache_on_ = false; int dns_cache_save_interval_seconds_ = 1; absl::optional network_thread_priority_ = absl::nullopt; @@ -289,10 +181,6 @@ class EngineBuilder { // This is the same value Cronet uses for QUIC: // https://source.chromium.org/chromium/chromium/src/+/main:net/quic/quic_context.h;drc=ccfe61524368c94b138ddf96ae8121d7eb7096cf;l=87 int32_t socket_receive_buffer_size_ = 1024 * 1024; // 1MB - -#ifdef ENVOY_MOBILE_XDS - absl::optional xds_builder_ = absl::nullopt; -#endif }; using EngineBuilderSharedPtr = std::shared_ptr; diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java index 823f47720b0d..43eca4f908c6 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java @@ -62,20 +62,6 @@ public enum TrustChainVerification { public final Map runtimeGuards; public final Boolean enablePlatformCertificatesValidation; public final String upstreamTlsSni; - public final String rtdsResourceName; - public final Integer rtdsTimeoutSeconds; - public final String xdsAddress; - public final Integer xdsPort; - public final Map xdsGrpcInitialMetadata; - public final String xdsRootCerts; - public final String nodeId; - public final String nodeRegion; - public final String nodeZone; - public final String nodeSubZone; - public final Struct nodeMetadata; - public final String cdsResourcesLocator; - public final Integer cdsTimeoutSeconds; - public final Boolean enableCds; private static final Pattern UNRESOLVED_KEY_PATTERN = Pattern.compile("\\{\\{ (.+) \\}\\}"); @@ -139,25 +125,6 @@ public enum TrustChainVerification { * @param keyValueStores platform key-value store implementations. * @param enablePlatformCertificatesValidation whether to use the platform verifier. * @param upstreamTlsSni the upstream TLS socket SNI override. - * @param rtdsResourceName the RTDS layer name for this client. - * @param rtdsTimeoutSeconds the timeout for RTDS fetches. - * @param xdsAddress the address for the xDS management server. - * @param xdsPort the port for the xDS server. - * @param xdsGrpcInitialMetadata The Headers (as key/value pairs) that must - * be included in the xDs gRPC stream's - * initial metadata (as HTTP headers). - * @param xdsRootCerts the root certificates to use for the TLS - * handshake during connection establishment - * with the xDS management server. - * @param nodeId the node ID in the Node metadata. - * @param nodeRegion the node region in the Node metadata. - * @param nodeZone the node zone in the Node metadata. - * @param nodeSubZone the node sub-zone in the Node metadata. - * @param nodeMetadata the node metadata. - * @param cdsResourcesLocator the resources locator for CDS. - * @param cdsTimeoutSeconds the timeout for CDS fetches. - * @param enableCds enables CDS, used because all CDS params - * could be empty. */ public EnvoyConfiguration( int connectTimeoutSeconds, int dnsRefreshSeconds, int dnsFailureRefreshSecondsBase, @@ -175,11 +142,7 @@ public EnvoyConfiguration( List httpPlatformFilterFactories, Map stringAccessors, Map keyValueStores, Map runtimeGuards, - boolean enablePlatformCertificatesValidation, String upstreamTlsSni, String rtdsResourceName, - Integer rtdsTimeoutSeconds, String xdsAddress, Integer xdsPort, - Map xdsGrpcInitialMetadata, String xdsRootCerts, String nodeId, - String nodeRegion, String nodeZone, String nodeSubZone, Struct nodeMetadata, - String cdsResourcesLocator, Integer cdsTimeoutSeconds, boolean enableCds) { + boolean enablePlatformCertificatesValidation, String upstreamTlsSni) { JniLibrary.load(); this.connectTimeoutSeconds = connectTimeoutSeconds; this.dnsRefreshSeconds = dnsRefreshSeconds; @@ -235,20 +198,6 @@ public EnvoyConfiguration( } this.enablePlatformCertificatesValidation = enablePlatformCertificatesValidation; this.upstreamTlsSni = upstreamTlsSni; - this.rtdsResourceName = rtdsResourceName; - this.rtdsTimeoutSeconds = rtdsTimeoutSeconds; - this.xdsAddress = xdsAddress; - this.xdsPort = xdsPort; - this.xdsGrpcInitialMetadata = new HashMap<>(xdsGrpcInitialMetadata); - this.xdsRootCerts = xdsRootCerts; - this.nodeId = nodeId; - this.nodeRegion = nodeRegion; - this.nodeZone = nodeZone; - this.nodeSubZone = nodeSubZone; - this.nodeMetadata = nodeMetadata; - this.cdsResourcesLocator = cdsResourcesLocator; - this.cdsTimeoutSeconds = cdsTimeoutSeconds; - this.enableCds = enableCds; } public long createBootstrap() { @@ -262,7 +211,6 @@ public long createBootstrap() { byte[][] runtimeGuards = JniBridgeUtility.mapToJniBytes(this.runtimeGuards); byte[][] quicHints = JniBridgeUtility.mapToJniBytes(this.quicHints); byte[][] quicSuffixes = JniBridgeUtility.stringsToJniBytes(quicCanonicalSuffixes); - byte[][] xdsGrpcInitialMetadata = JniBridgeUtility.mapToJniBytes(this.xdsGrpcInitialMetadata); return JniLibrary.createBootstrap( connectTimeoutSeconds, dnsRefreshSeconds, dnsFailureRefreshSecondsBase, @@ -273,10 +221,7 @@ public long createBootstrap() { enableSocketTagging, enableInterfaceBinding, h2ConnectionKeepaliveIdleIntervalMilliseconds, h2ConnectionKeepaliveTimeoutSeconds, maxConnectionsPerHost, streamIdleTimeoutSeconds, perTryIdleTimeoutSeconds, appVersion, appId, enforceTrustChainVerification, filterChain, - enablePlatformCertificatesValidation, upstreamTlsSni, runtimeGuards, rtdsResourceName, - rtdsTimeoutSeconds, xdsAddress, xdsPort, xdsGrpcInitialMetadata, xdsRootCerts, nodeId, - nodeRegion, nodeZone, nodeSubZone, nodeMetadata.toByteArray(), cdsResourcesLocator, - cdsTimeoutSeconds, enableCds); + enablePlatformCertificatesValidation, upstreamTlsSni, runtimeGuards); } static class ConfigurationException extends RuntimeException { diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index c76fba6a94f1..fca73d8d2c0e 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -301,9 +301,5 @@ public static native long createBootstrap( long h2ConnectionKeepaliveTimeoutSeconds, long maxConnectionsPerHost, long streamIdleTimeoutSeconds, long perTryIdleTimeoutSeconds, String appVersion, String appId, boolean trustChainVerification, byte[][] filterChain, - boolean enablePlatformCertificatesValidation, String upstreamTlsSni, byte[][] runtimeGuards, - String rtdsResourceName, long rtdsTimeoutSeconds, String xdsAddress, long xdsPort, - byte[][] xdsGrpcInitialMetadata, String xdsRootCerts, String nodeId, String nodeRegion, - String nodeZone, String nodeSubZone, byte[] nodeMetadata, String cdsResourcesLocator, - long cdsTimeoutSeconds, boolean enableCds); + boolean enablePlatformCertificatesValidation, String upstreamTlsSni, byte[][] runtimeGuards); } diff --git a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java index 8c5b16c7c733..31685bb778b3 100644 --- a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java +++ b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java @@ -61,10 +61,7 @@ public class NativeCronvoyEngineBuilderImpl extends CronvoyEngineBuilderImpl { private TrustChainVerification mTrustChainVerification = VERIFY_TRUST_CHAIN; private final boolean mEnablePlatformCertificatesValidation = true; private String mUpstreamTlsSni = ""; - private final String mNodeId = ""; - private final String mNodeRegion = ""; - private final String mNodeZone = ""; - private final String mNodeSubZone = ""; + private final Map mRuntimeGuards = new HashMap<>(); /** @@ -253,11 +250,6 @@ mEnableGzipDecompression, brotliEnabled(), portMigrationEnabled(), mEnableSocket mH2ConnectionKeepaliveTimeoutSeconds, mMaxConnectionsPerHost, mStreamIdleTimeoutSeconds, mPerTryIdleTimeoutSeconds, mAppVersion, mAppId, mTrustChainVerification, nativeFilterChain, platformFilterChain, stringAccessors, keyValueStores, mRuntimeGuards, - mEnablePlatformCertificatesValidation, mUpstreamTlsSni, - /*rtdsResourceName=*/"", /*rtdsTimeoutSeconds=*/0, /*xdsAddress=*/"", - /*xdsPort=*/0, /*xdsGrpcInitialMetadata=*/Collections.emptyMap(), - /*xdsSslRootCerts=*/"", mNodeId, mNodeRegion, mNodeZone, mNodeSubZone, - Struct.getDefaultInstance(), /*cdsResourcesLocator=*/"", /*cdsTimeoutSeconds=*/0, - /*enableCds=*/false); + mEnablePlatformCertificatesValidation, mUpstreamTlsSni); } } diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index 35da3faf067f..25eced8b83b9 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -1151,9 +1151,7 @@ void configureBuilder(Envoy::JNI::JniHelper& jni_helper, jlong connect_timeout_s jlong stream_idle_timeout_seconds, jlong per_try_idle_timeout_seconds, jstring app_version, jstring app_id, jboolean trust_chain_verification, jobjectArray filter_chain, jboolean enable_platform_certificates_validation, - jstring upstream_tls_sni, jobjectArray runtime_guards, jstring node_id, - jstring node_region, jstring node_zone, jstring node_sub_zone, - jbyteArray serialized_node_metadata, + jstring upstream_tls_sni, jobjectArray runtime_guards, Envoy::Platform::EngineBuilder& builder) { builder.addConnectTimeoutSeconds((connect_timeout_seconds)); builder.addDnsRefreshSeconds((dns_refresh_seconds)); @@ -1216,19 +1214,6 @@ void configureBuilder(Envoy::JNI::JniHelper& jni_helper, jlong connect_timeout_s std::vector hostnames = javaObjectArrayToStringVector(jni_helper, dns_preresolve_hostnames); builder.addDnsPreresolveHostnames(hostnames); - std::string native_node_id = Envoy::JNI::javaStringToCppString(jni_helper, node_id); - if (!native_node_id.empty()) { - builder.setNodeId(native_node_id); - } - std::string native_node_region = Envoy::JNI::javaStringToCppString(jni_helper, node_region); - if (!native_node_region.empty()) { - builder.setNodeLocality(native_node_region, - Envoy::JNI::javaStringToCppString(jni_helper, node_zone), - Envoy::JNI::javaStringToCppString(jni_helper, node_sub_zone)); - } - Envoy::ProtobufWkt::Struct node_metadata; - Envoy::JNI::javaByteArrayToProto(jni_helper, serialized_node_metadata, &node_metadata); - builder.setNodeMetadata(node_metadata); } #if defined(__GNUC__) @@ -1262,11 +1247,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr jlong stream_idle_timeout_seconds, jlong per_try_idle_timeout_seconds, jstring app_version, jstring app_id, jboolean trust_chain_verification, jobjectArray filter_chain, jboolean enable_platform_certificates_validation, jstring upstream_tls_sni, - jobjectArray runtime_guards, jstring rtds_resource_name, jlong rtds_timeout_seconds, - jstring xds_address, jlong xds_port, jobjectArray xds_grpc_initial_metadata, - jstring xds_root_certs, jstring node_id, jstring node_region, jstring node_zone, - jstring node_sub_zone, jbyteArray serialized_node_metadata, jstring cds_resources_locator, - jlong cds_timeout_seconds, jboolean enable_cds) { + jobjectArray runtime_guards) { Envoy::JNI::JniHelper jni_helper(env); Envoy::Platform::EngineBuilder builder; @@ -1281,40 +1262,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr h2_connection_keepalive_idle_interval_milliseconds, h2_connection_keepalive_timeout_seconds, max_connections_per_host, stream_idle_timeout_seconds, per_try_idle_timeout_seconds, app_version, app_id, trust_chain_verification, filter_chain, - enable_platform_certificates_validation, upstream_tls_sni, runtime_guards, node_id, - node_region, node_zone, node_sub_zone, serialized_node_metadata, builder); - - std::string native_xds_address = Envoy::JNI::javaStringToCppString(jni_helper, xds_address); - if (!native_xds_address.empty()) { -#ifdef ENVOY_MOBILE_XDS - Envoy::Platform::XdsBuilder xds_builder(std::move(native_xds_address), xds_port); - auto initial_metadata = - javaObjectArrayToStringPairVector(jni_helper, xds_grpc_initial_metadata); - for (const std::pair& entry : initial_metadata) { - xds_builder.addInitialStreamHeader(entry.first, entry.second); - } - std::string native_root_certs = Envoy::JNI::javaStringToCppString(jni_helper, xds_root_certs); - if (!native_root_certs.empty()) { - xds_builder.setSslRootCerts(std::move(native_root_certs)); - } - std::string native_rtds_resource_name = - Envoy::JNI::javaStringToCppString(jni_helper, rtds_resource_name); - if (!native_rtds_resource_name.empty()) { - xds_builder.addRuntimeDiscoveryService(std::move(native_rtds_resource_name), - rtds_timeout_seconds); - } - if (enable_cds == JNI_TRUE) { - xds_builder.addClusterDiscoveryService( - Envoy::JNI::javaStringToCppString(jni_helper, cds_resources_locator), - cds_timeout_seconds); - } - builder.setXds(std::move(xds_builder)); -#else - jni_helper.throwNew("java/lang/UnsupportedOperationException", - "This library does not support xDS. Please use " - "io.envoyproxy.envoymobile:envoy-xds instead."); -#endif - } + enable_platform_certificates_validation, upstream_tls_sni, runtime_guards, builder); return reinterpret_cast(builder.generateBootstrap().release()); } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/BUILD b/mobile/library/kotlin/io/envoyproxy/envoymobile/BUILD index ca8177a99157..c35b15bd5c3e 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/BUILD +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/BUILD @@ -24,28 +24,6 @@ android_artifacts( visibility = ["//visibility:public"], ) -android_artifacts( - name = "envoy_xds_aar", - android_library = ":envoy_lib", - archive_name = "envoy_xds", - manifest = "EnvoyManifest.xml", - native_deps = select({ - "@envoy//bazel:opt_build": ["//library/jni:libenvoy_jni.so.debug_info"], - "//conditions:default": ["//library/jni:libenvoy_jni.so"], - }), - proguard_rules = "//library:proguard_rules", - substitutions = { - "{pom_artifact_id}": "envoy-xds", - "{pom_extra_dependencies}": """ - - com.google.protobuf - protobuf-javalite - 3.24.4 - """, - }, - visibility = ["//visibility:public"], -) - kt_android_library( name = "envoy_lib", srcs = [ diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt index d62db835933a..fb67a999ca29 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt @@ -1,6 +1,5 @@ package io.envoyproxy.envoymobile -import com.google.protobuf.Struct import io.envoyproxy.envoymobile.engine.EnvoyConfiguration import io.envoyproxy.envoymobile.engine.EnvoyConfiguration.TrustChainVerification import io.envoyproxy.envoymobile.engine.EnvoyEngine @@ -11,109 +10,6 @@ import io.envoyproxy.envoymobile.engine.types.EnvoyKeyValueStore import io.envoyproxy.envoymobile.engine.types.EnvoyStringAccessor import java.util.UUID -/** - * Builder for generating the xDS configuration for the Envoy Mobile engine. xDS is a protocol for - * dynamic configuration of Envoy instances, more information can be found in - * https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol. - * - * This class is typically used as input to the EngineBuilder's setXds() method. - */ -open class XdsBuilder(internal val xdsServerAddress: String, internal val xdsServerPort: Int) { - companion object { - private const val DEFAULT_XDS_TIMEOUT_IN_SECONDS: Int = 5 - } - - internal var grpcInitialMetadata = mutableMapOf() - internal var sslRootCerts: String? = null - internal var rtdsResourceName: String? = null - internal var rtdsTimeoutInSeconds: Int = DEFAULT_XDS_TIMEOUT_IN_SECONDS - internal var enableCds: Boolean = false - internal var cdsResourcesLocator: String? = null - internal var cdsTimeoutInSeconds: Int = DEFAULT_XDS_TIMEOUT_IN_SECONDS - - /** - * Adds a header to the initial HTTP metadata headers sent on the gRPC stream. - * - * A common use for the initial metadata headers is for authentication to the xDS management - * server. - * - * For example, if using API keys to authenticate to Traffic Director on GCP (see - * https://cloud.google.com/docs/authentication/api-keys for details), invoke: - * builder.addInitialStreamHeader("x-goog-api-key", apiKeyToken) - * .addInitialStreamHeader("X-Android-Package", appPackageName) - * .addInitialStreamHeader("X-Android-Cert", sha1KeyFingerprint) - * - * @param header The HTTP header name to add to the initial gRPC stream's metadata. - * @param value The HTTP header value to add to the initial gRPC stream's metadata. - * @return this builder. - */ - fun addInitialStreamHeader(header: String, value: String): XdsBuilder { - this.grpcInitialMetadata.put(header, value) - return this - } - - /** - * Sets the PEM-encoded server root certificates used to negotiate the TLS handshake for the gRPC - * connection. If no root certs are specified, the operating system defaults are used. - * - * @param rootCerts The PEM-encoded server root certificates. - * @return this builder. - */ - fun setSslRootCerts(rootCerts: String): XdsBuilder { - this.sslRootCerts = rootCerts - return this - } - - /** - * Adds Runtime Discovery Service (RTDS) to the Runtime layers of the Bootstrap configuration, to - * retrieve dynamic runtime configuration via the xDS management server. - * - * @param resourceName The runtime config resource to subscribe to. - * @param timeoutInSeconds specifies the `initial_fetch_timeout` field on the - * api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch - * timeout value of 5s, to prevent mobile app initialization from stalling. The default - * parameter value may change through the course of experimentation and no assumptions should be - * made of its exact value. - * @return this builder. - */ - fun addRuntimeDiscoveryService( - resourceName: String, - timeoutInSeconds: Int = DEFAULT_XDS_TIMEOUT_IN_SECONDS - ): XdsBuilder { - this.rtdsResourceName = resourceName - this.rtdsTimeoutInSeconds = timeoutOrXdsDefault(timeoutInSeconds) - return this - } - - /** - * Adds the Cluster Discovery Service (CDS) configuration for retrieving dynamic cluster resources - * via the xDS management server. - * - * @param cdsResourcesLocator the xdstp:// URI for subscribing to the cluster - * resources. If not using xdstp, then `cds_resources_locator` should be set to the empty - * string. - * @param timeoutInSeconds specifies the `initial_fetch_timeout` field on the - * api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch - * timeout value of 5s, to prevent mobile app initialization from stalling. The default - * parameter value may change through the course of experimentation and no assumptions should be - * made of its exact value. - * @return this builder. - */ - fun addClusterDiscoveryService( - cdsResourcesLocator: String? = null, - timeoutInSeconds: Int = DEFAULT_XDS_TIMEOUT_IN_SECONDS - ): XdsBuilder { - this.enableCds = true - this.cdsResourcesLocator = cdsResourcesLocator - this.cdsTimeoutInSeconds = timeoutOrXdsDefault(timeoutInSeconds) - return this - } - - private fun timeoutOrXdsDefault(timeout: Int): Int { - return if (timeout > 0) timeout else DEFAULT_XDS_TIMEOUT_IN_SECONDS - } -} - /** Builder used for creating and running a new `Engine` instance. */ open class EngineBuilder() { protected var onEngineRunning: (() -> Unit) = {} @@ -165,12 +61,6 @@ open class EngineBuilder() { private var keyValueStores = mutableMapOf() private var enablePlatformCertificatesValidation = false private var upstreamTlsSni: String = "" - private var nodeId: String = "" - private var nodeRegion: String = "" - private var nodeZone: String = "" - private var nodeSubZone: String = "" - private var nodeMetadata: Struct = Struct.getDefaultInstance() - private var xdsBuilder: XdsBuilder? = null /** * Sets a log level to use with Envoy. @@ -582,54 +472,6 @@ open class EngineBuilder() { return this } - /** - * Sets the node.id field in the Bootstrap configuration. - * - * @param nodeId the node ID. - * @return this builder. - */ - fun setNodeId(nodeId: String): EngineBuilder { - this.nodeId = nodeId - return this - } - - /** - * Sets the node.locality field in the Bootstrap configuration. - * - * @param region the region of the node locality. - * @param zone the zone of the node locality. - * @param subZone the sub-zone of the node locality. - * @return this builder. - */ - fun setNodeLocality(region: String, zone: String, subZone: String): EngineBuilder { - this.nodeRegion = region - this.nodeZone = zone - this.nodeSubZone = subZone - return this - } - - /** - * Sets the node.metadata field in the Bootstrap configuration. - * - * @param metadata the metadata of the node. - * @return this builder. - */ - fun setNodeMetadata(metadata: Struct): EngineBuilder { - this.nodeMetadata = metadata - return this - } - - /** - * Sets the xDS configuration for the Envoy Mobile engine. - * - * @param xdsBuilder The XdsBuilder instance from which to construct the xDS configuration. - * @return this builder. - */ - fun setXds(xdsBuilder: XdsBuilder): EngineBuilder { - this.xdsBuilder = xdsBuilder - return this - } - /** * Adds a runtime guard for the `envoy.reloadable_features.`. For example if the runtime * guard is `envoy.reloadable_features.use_foo`, the guard name is `use_foo`. @@ -712,20 +554,6 @@ open class EngineBuilder() { runtimeGuards, enablePlatformCertificatesValidation, upstreamTlsSni, - xdsBuilder?.rtdsResourceName, - xdsBuilder?.rtdsTimeoutInSeconds ?: 0, - xdsBuilder?.xdsServerAddress, - xdsBuilder?.xdsServerPort ?: 0, - xdsBuilder?.grpcInitialMetadata ?: mapOf(), - xdsBuilder?.sslRootCerts, - nodeId, - nodeRegion, - nodeZone, - nodeSubZone, - nodeMetadata, - xdsBuilder?.cdsResourcesLocator, - xdsBuilder?.cdsTimeoutInSeconds ?: 0, - xdsBuilder?.enableCds ?: false, ) return EngineImpl(engineType(), engineConfiguration, logLevel) diff --git a/mobile/library/objective-c/EnvoyConfiguration.h b/mobile/library/objective-c/EnvoyConfiguration.h index 81d0e209e682..6f6b1520422c 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.h +++ b/mobile/library/objective-c/EnvoyConfiguration.h @@ -46,19 +46,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) NSArray *httpPlatformFilterFactories; @property (nonatomic, strong) NSDictionary *stringAccessors; @property (nonatomic, strong) NSDictionary> *keyValueStores; -@property (nonatomic, strong, nullable) NSString *nodeId; -@property (nonatomic, strong, nullable) NSString *nodeRegion; -@property (nonatomic, strong, nullable) NSString *nodeZone; -@property (nonatomic, strong, nullable) NSString *nodeSubZone; -@property (nonatomic, strong, nullable) NSString *xdsServerAddress; -@property (nonatomic, assign) UInt32 xdsServerPort; -@property (nonatomic, strong) NSDictionary *xdsGrpcInitialMetadata; -@property (nonatomic, strong, nullable) NSString *xdsSslRootCerts; -@property (nonatomic, strong, nullable) NSString *rtdsResourceName; -@property (nonatomic, assign) UInt32 rtdsTimeoutSeconds; -@property (nonatomic, assign) BOOL enableCds; -@property (nonatomic, strong, nullable) NSString *cdsResourcesLocator; -@property (nonatomic, assign) UInt32 cdsTimeoutSeconds; @property (nonatomic, assign) intptr_t bootstrapPointer; /** @@ -104,21 +91,7 @@ NS_ASSUME_NONNULL_BEGIN stringAccessors keyValueStores: (NSDictionary> *) - keyValueStores - nodeId:(nullable NSString *)nodeId - nodeRegion:(nullable NSString *)nodeRegion - nodeZone:(nullable NSString *)nodeZone - nodeSubZone:(nullable NSString *)nodeSubZone - xdsServerAddress:(nullable NSString *)xdsServerAddress - xdsServerPort:(UInt32)xdsServerPort - xdsGrpcInitialMetadata: - (NSDictionary *)xdsGrpcInitialMetadata - xdsSslRootCerts:(nullable NSString *)xdsSslRootCerts - rtdsResourceName:(nullable NSString *)rtdsResourceName - rtdsTimeoutSeconds:(UInt32)rtdsTimeoutSeconds - enableCds:(BOOL)enableCds - cdsResourcesLocator:(nullable NSString *)cdsResourcesLocator - cdsTimeoutSeconds:(UInt32)cdsTimeoutSeconds; + keyValueStores; /** Generate a string description of the C++ Envoy bootstrap from this configuration. diff --git a/mobile/library/objective-c/EnvoyConfiguration.mm b/mobile/library/objective-c/EnvoyConfiguration.mm index 8285569e9a3d..bf3591564782 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.mm +++ b/mobile/library/objective-c/EnvoyConfiguration.mm @@ -107,21 +107,7 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds stringAccessors keyValueStores: (NSDictionary> *) - keyValueStores - nodeId:(nullable NSString *)nodeId - nodeRegion:(nullable NSString *)nodeRegion - nodeZone:(nullable NSString *)nodeZone - nodeSubZone:(nullable NSString *)nodeSubZone - xdsServerAddress:(nullable NSString *)xdsServerAddress - xdsServerPort:(UInt32)xdsServerPort - xdsGrpcInitialMetadata: - (NSDictionary *)xdsGrpcInitialMetadata - xdsSslRootCerts:(nullable NSString *)xdsSslRootCerts - rtdsResourceName:(nullable NSString *)rtdsResourceName - rtdsTimeoutSeconds:(UInt32)rtdsTimeoutSeconds - enableCds:(BOOL)enableCds - cdsResourcesLocator:(nullable NSString *)cdsResourcesLocator - cdsTimeoutSeconds:(UInt32)cdsTimeoutSeconds { + keyValueStores { self = [super init]; if (!self) { return nil; @@ -161,19 +147,6 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds self.httpPlatformFilterFactories = httpPlatformFilterFactories; self.stringAccessors = stringAccessors; self.keyValueStores = keyValueStores; - self.nodeId = nodeId; - self.nodeRegion = nodeRegion; - self.nodeZone = nodeZone; - self.nodeSubZone = nodeSubZone; - self.xdsServerAddress = xdsServerAddress; - self.xdsServerPort = xdsServerPort; - self.xdsGrpcInitialMetadata = xdsGrpcInitialMetadata; - self.xdsSslRootCerts = xdsSslRootCerts; - self.rtdsResourceName = rtdsResourceName; - self.rtdsTimeoutSeconds = rtdsTimeoutSeconds; - self.cdsResourcesLocator = cdsResourcesLocator; - self.cdsTimeoutSeconds = cdsTimeoutSeconds; - self.enableCds = enableCds; self.bootstrapPointer = 0; return self; @@ -247,36 +220,6 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds if (self.upstreamTlsSni != nil) { builder.setUpstreamTlsSni([self.upstreamTlsSni toCXXString]); } - if (self.nodeRegion != nil) { - builder.setNodeLocality([self.nodeRegion toCXXString], [self.nodeZone toCXXString], - [self.nodeSubZone toCXXString]); - } - if (self.nodeId != nil) { - builder.setNodeId([self.nodeId toCXXString]); - } - -#ifdef ENVOY_MOBILE_XDS - if (self.xdsServerAddress != nil) { - Envoy::Platform::XdsBuilder xdsBuilder([self.xdsServerAddress toCXXString], self.xdsServerPort); - for (NSString *header in self.xdsGrpcInitialMetadata) { - xdsBuilder.addInitialStreamHeader( - [header toCXXString], [[self.xdsGrpcInitialMetadata objectForKey:header] toCXXString]); - } - if (self.xdsSslRootCerts != nil) { - xdsBuilder.setSslRootCerts([self.xdsSslRootCerts toCXXString]); - } - if (self.rtdsResourceName != nil) { - xdsBuilder.addRuntimeDiscoveryService([self.rtdsResourceName toCXXString], - self.rtdsTimeoutSeconds); - } - if (self.enableCds) { - xdsBuilder.addClusterDiscoveryService( - self.cdsResourcesLocator != nil ? [self.cdsResourcesLocator toCXXString] : "", - self.cdsTimeoutSeconds); - } - builder.setXds(xdsBuilder); - } -#endif return builder; } diff --git a/mobile/library/swift/EngineBuilder.swift b/mobile/library/swift/EngineBuilder.swift index fcadd8685fa1..f8ee3891ed82 100644 --- a/mobile/library/swift/EngineBuilder.swift +++ b/mobile/library/swift/EngineBuilder.swift @@ -1,122 +1,6 @@ @_implementationOnly import EnvoyEngine import Foundation -#if ENVOY_MOBILE_XDS -/// Builder for generating the xDS configuration for the Envoy Mobile engine. -/// xDS is a protocol for dynamic configuration of Envoy instances, more information can be found in -/// https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol. -/// -/// This class is typically used as input to the EngineBuilder's setXds() method. -@objcMembers -open class XdsBuilder: NSObject { - public static let defaultXdsTimeoutInSeconds: UInt32 = 5 - - let xdsServerAddress: String - let xdsServerPort: UInt32 - var xdsGrpcInitialMetadata: [String: String] = [:] - var sslRootCerts: String? - var rtdsResourceName: String? - var rtdsTimeoutInSeconds: UInt32 = 0 - var enableCds: Bool = false - var cdsResourcesLocator: String? - var cdsTimeoutInSeconds: UInt32 = 0 - - /// Initialize a new builder for xDS configuration. - /// - /// - parameter xdsServerAddress: The host name or IP address of the xDS management server. - /// - parameter xdsServerPort: The port on which the server listens for client connections. - public init(xdsServerAddress: String, xdsServerPort: UInt32) { - self.xdsServerAddress = xdsServerAddress - self.xdsServerPort = xdsServerPort - } - - /// Adds a header to the initial HTTP metadata headers sent on the gRPC stream. - /// - /// A common use for the initial metadata headers is for authentication to the xDS management - /// server. - /// - /// For example, if using API keys to authenticate to Traffic Director on GCP (see - /// https://cloud.google.com/docs/authentication/api-keys for details), invoke: - /// builder.addInitialStreamHeader("x-goog-api-key", apiKeyToken) - /// .addInitialStreamHeader("X-Android-Package", appPackageName) - /// .addInitialStreamHeader("X-Android-Cert", sha1KeyFingerprint); - /// - /// - parameter header: The HTTP header to add on the gRPC stream's initial metadata. - /// - parameter value: The HTTP header value to add on the gRPC stream's initial metadata. - /// - /// - returns: This builder. - @discardableResult - public func addInitialStreamHeader( - header: String, - value: String) -> Self { - self.xdsGrpcInitialMetadata[header] = value - return self - } - - /// Sets the PEM-encoded server root certificates used to negotiate the TLS handshake for the gRPC - /// connection. If no root certs are specified, the operating system defaults are used. - /// - /// - parameter rootCerts: The PEM-encoded server root certificates. - /// - /// - returns: This builder. - @discardableResult - public func setSslRootCerts(rootCerts: String) -> Self { - self.sslRootCerts = rootCerts - return self - } - - /// Adds Runtime Discovery Service (RTDS) to the Runtime layers of the Bootstrap configuration, - /// to retrieve dynamic runtime configuration via the xDS management server. - /// - /// - parameter resourceName: The runtime config resource to subscribe to. - /// - parameter timeoutInSeconds: specifies the `initial_fetch_timeout` field on the - /// api.v3.core.ConfigSource. Unlike the ConfigSource default of - /// 15s, we set a default fetch timeout value of 5s, to prevent - /// mobile app initialization from stalling. The default parameter - /// value may change through the course of experimentation and no - /// assumptions should be made of its exact value. - /// - /// - returns: This builder. - @discardableResult - public func addRuntimeDiscoveryService( - resourceName: String, - timeoutInSeconds: UInt32 = XdsBuilder.defaultXdsTimeoutInSeconds) -> Self { - self.rtdsResourceName = resourceName - self.rtdsTimeoutInSeconds = timeoutOrXdsDefault(timeoutInSeconds) - return self - } - - /// Adds the Cluster Discovery Service (CDS) configuration for retrieving dynamic cluster - /// resources via the xDS management server. - /// - /// - parameter cdsResourcesLocator: the xdstp:// URI for subscribing to the cluster - /// resources. If not using xdstp, then `cds_resources_locator` - /// should be set to the empty string. - /// - parameter timeoutInSeconds: specifies the `initial_fetch_timeout` field on the - /// api.v3.core.ConfigSource. Unlike the ConfigSource default of - /// 15s, we set a default fetch timeout value of 5s, to prevent - /// mobile app initialization from stalling. The default - /// parameter value may change through the course of - /// experimentation and no assumptions should be made of its - /// exact value. - /// - /// - returns: This builder. - @discardableResult - public func addClusterDiscoveryService( - cdsResourcesLocator: String? = nil, - timeoutInSeconds: UInt32 = XdsBuilder.defaultXdsTimeoutInSeconds) -> Self { - self.enableCds = true - self.cdsResourcesLocator = cdsResourcesLocator - self.cdsTimeoutInSeconds = timeoutOrXdsDefault(timeoutInSeconds) - return self - } - - private func timeoutOrXdsDefault(_ timeout: UInt32) -> UInt32 { - return timeout > 0 ? timeout : XdsBuilder.defaultXdsTimeoutInSeconds - } -} -#endif - /// Builder used for creating and running a new Engine instance. @objcMembers open class EngineBuilder: NSObject { @@ -164,13 +48,6 @@ open class EngineBuilder: NSObject { private var stringAccessors: [String: EnvoyStringAccessor] = [:] private var keyValueStores: [String: EnvoyKeyValueStore] = [:] private var runtimeGuards: [String: Bool] = [:] - private var nodeID: String? - private var nodeRegion: String? - private var nodeZone: String? - private var nodeSubZone: String? -#if ENVOY_MOBILE_XDS - private var xdsBuilder: XdsBuilder? -#endif // MARK: - Public @@ -634,50 +511,6 @@ open class EngineBuilder: NSObject { return self } - /// Sets the node.id field in the Bootstrap configuration. - /// - /// - parameter nodeID: The node ID. - /// - /// - returns: This builder. - @discardableResult - public func setNodeID(_ nodeID: String) -> Self { - self.nodeID = nodeID - return self - } - - /// Sets the node locality in the Bootstrap configuration. - /// - /// - parameter region: The region. - /// - parameter zone: The zone. - /// - parameter subZone: The sub-zone. - /// - /// - returns: This builder. - @discardableResult - public func setNodeLocality( - region: String, - zone: String, - subZone: String - ) -> Self { - self.nodeRegion = region - self.nodeZone = zone - self.nodeSubZone = subZone - return self - } - -#if ENVOY_MOBILE_XDS - /// Sets the xDS configuration for the Envoy Mobile engine. - /// - /// - parameter xdsBuilder: The XdsBuilder instance which specifies the xDS config options. - /// The EngineBuilder takes ownership over the xds_builder. - /// - /// - returns: This builder. - @discardableResult - public func setXds(_ xdsBuilder: XdsBuilder) -> Self { - self.xdsBuilder = xdsBuilder - return self - } -#endif - /// Builds and runs a new `Engine` instance with the provided configuration. /// /// - note: Must be strongly retained in order for network requests to be performed correctly. @@ -717,28 +550,6 @@ open class EngineBuilder: NSObject { } func makeConfig() -> EnvoyConfiguration { - var xdsServerAddress: String? - var xdsServerPort: UInt32 = 0 - var xdsGrpcInitialMetadata: [String: String] = [:] - var xdsSslRootCerts: String? - var rtdsResourceName: String? - var rtdsTimeoutSeconds: UInt32 = 0 - var enableCds: Bool = false - var cdsResourcesLocator: String? - var cdsTimeoutSeconds: UInt32 = 0 - -#if ENVOY_MOBILE_XDS - xdsServerAddress = self.xdsBuilder?.xdsServerAddress - xdsServerPort = self.xdsBuilder?.xdsServerPort ?? 0 - xdsGrpcInitialMetadata = self.xdsBuilder?.xdsGrpcInitialMetadata ?? [:] - xdsSslRootCerts = self.xdsBuilder?.sslRootCerts - rtdsResourceName = self.xdsBuilder?.rtdsResourceName - rtdsTimeoutSeconds = self.xdsBuilder?.rtdsTimeoutInSeconds ?? 0 - enableCds = self.xdsBuilder?.enableCds ?? false - cdsResourcesLocator = self.xdsBuilder?.cdsResourcesLocator - cdsTimeoutSeconds = self.xdsBuilder?.cdsTimeoutInSeconds ?? 0 -#endif - return EnvoyConfiguration( connectTimeoutSeconds: self.connectTimeoutSeconds, dnsRefreshSeconds: self.dnsRefreshSeconds, @@ -773,20 +584,7 @@ open class EngineBuilder: NSObject { nativeFilterChain: self.nativeFilterChain, platformFilterChain: self.platformFilterChain, stringAccessors: self.stringAccessors, - keyValueStores: self.keyValueStores, - nodeId: self.nodeID, - nodeRegion: self.nodeRegion, - nodeZone: self.nodeZone, - nodeSubZone: self.nodeSubZone, - xdsServerAddress: xdsServerAddress, - xdsServerPort: xdsServerPort, - xdsGrpcInitialMetadata: xdsGrpcInitialMetadata, - xdsSslRootCerts: xdsSslRootCerts, - rtdsResourceName: rtdsResourceName, - rtdsTimeoutSeconds: rtdsTimeoutSeconds, - enableCds: enableCds, - cdsResourcesLocator: cdsResourcesLocator, - cdsTimeoutSeconds: cdsTimeoutSeconds + keyValueStores: self.keyValueStores ) } diff --git a/mobile/test/cc/unit/envoy_config_test.cc b/mobile/test/cc/unit/envoy_config_test.cc index 432ce250c2f8..37a8551d5c0f 100644 --- a/mobile/test/cc/unit/envoy_config_test.cc +++ b/mobile/test/cc/unit/envoy_config_test.cc @@ -362,78 +362,6 @@ TEST(TestConfig, SocketReceiveBufferSize) { } #endif -#ifdef ENVOY_MOBILE_XDS -TEST(TestConfig, XdsConfig) { - EngineBuilder engine_builder; - const std::string host = "fake-td.googleapis.com"; - const uint32_t port = 12345; - const std::string authority = absl::StrCat(host, ":", port); - - XdsBuilder xds_builder(/*xds_server_address=*/host, - /*xds_server_port=*/port); - engine_builder.setXds(std::move(xds_builder)); - std::unique_ptr bootstrap = engine_builder.generateBootstrap(); - - auto& ads_config = bootstrap->dynamic_resources().ads_config(); - EXPECT_EQ(ads_config.api_type(), envoy::config::core::v3::ApiConfigSource::GRPC); - EXPECT_EQ(ads_config.grpc_services(0).envoy_grpc().cluster_name(), "base"); - EXPECT_EQ(ads_config.grpc_services(0).envoy_grpc().authority(), authority); - - Protobuf::RepeatedPtrField - expected_dns_preresolve_hostnames; - auto& host_addr1 = *expected_dns_preresolve_hostnames.Add(); - host_addr1.set_address(host); - host_addr1.set_port_value(port); - EXPECT_TRUE(TestUtility::repeatedPtrFieldEqual( - getDfpClusterConfig(*bootstrap).dns_cache_config().preresolve_hostnames(), - expected_dns_preresolve_hostnames)); - - // With initial gRPC metadata. - xds_builder = XdsBuilder(/*xds_server_address=*/host, /*xds_server_port=*/port); - xds_builder.addInitialStreamHeader(/*header=*/"x-goog-api-key", /*value=*/"A1B2C3") - .addInitialStreamHeader(/*header=*/"x-android-package", - /*value=*/"com.google.envoymobile.io.myapp"); - engine_builder.setXds(std::move(xds_builder)); - bootstrap = engine_builder.generateBootstrap(); - auto& ads_config_with_metadata = bootstrap->dynamic_resources().ads_config(); - EXPECT_EQ(ads_config_with_metadata.api_type(), envoy::config::core::v3::ApiConfigSource::GRPC); - EXPECT_EQ(ads_config_with_metadata.grpc_services(0).envoy_grpc().cluster_name(), "base"); - EXPECT_EQ(ads_config_with_metadata.grpc_services(0).envoy_grpc().authority(), authority); - EXPECT_EQ(ads_config_with_metadata.grpc_services(0).initial_metadata(0).key(), "x-goog-api-key"); - EXPECT_EQ(ads_config_with_metadata.grpc_services(0).initial_metadata(0).value(), "A1B2C3"); - EXPECT_EQ(ads_config_with_metadata.grpc_services(0).initial_metadata(1).key(), - "x-android-package"); - EXPECT_EQ(ads_config_with_metadata.grpc_services(0).initial_metadata(1).value(), - "com.google.envoymobile.io.myapp"); -} - -TEST(TestConfig, MoveConstructor) { - EngineBuilder engine_builder; - engine_builder.addRuntimeGuard("test_feature_false", true).enableGzipDecompression(false); - - std::unique_ptr bootstrap = engine_builder.generateBootstrap(); - std::string bootstrap_str = bootstrap->ShortDebugString(); - EXPECT_THAT(bootstrap_str, HasSubstr("\"test_feature_false\" value { bool_value: true }")); - EXPECT_THAT(bootstrap_str, Not(HasSubstr("envoy.filters.http.decompressor"))); - - EngineBuilder engine_builder_move1(std::move(engine_builder)); - engine_builder_move1.enableGzipDecompression(true); - XdsBuilder xdsBuilder("FAKE_XDS_SERVER", 0); - xdsBuilder.addClusterDiscoveryService(); - engine_builder_move1.setXds(xdsBuilder); - bootstrap_str = engine_builder_move1.generateBootstrap()->ShortDebugString(); - EXPECT_THAT(bootstrap_str, HasSubstr("\"test_feature_false\" value { bool_value: true }")); - EXPECT_THAT(bootstrap_str, HasSubstr("envoy.filters.http.decompressor")); - EXPECT_THAT(bootstrap_str, HasSubstr("FAKE_XDS_SERVER")); - - EngineBuilder engine_builder_move2(std::move(engine_builder_move1)); - bootstrap_str = engine_builder_move2.generateBootstrap()->ShortDebugString(); - EXPECT_THAT(bootstrap_str, HasSubstr("\"test_feature_false\" value { bool_value: true }")); - EXPECT_THAT(bootstrap_str, HasSubstr("envoy.filters.http.decompressor")); - EXPECT_THAT(bootstrap_str, HasSubstr("FAKE_XDS_SERVER")); -} -#endif - TEST(TestConfig, EnablePlatformCertificatesValidation) { EngineBuilder engine_builder; engine_builder.enablePlatformCertificatesValidation(false); @@ -525,69 +453,5 @@ TEST(TestConfig, DISABLED_StringAccessors) { release_envoy_data(data); } -TEST(TestConfig, SetNodeId) { - EngineBuilder engine_builder; - const std::string default_node_id = "envoy-mobile"; - EXPECT_EQ(engine_builder.generateBootstrap()->node().id(), default_node_id); - - const std::string test_node_id = "my_test_node"; - engine_builder.setNodeId(test_node_id); - EXPECT_EQ(engine_builder.generateBootstrap()->node().id(), test_node_id); -} - -TEST(TestConfig, SetNodeLocality) { - EngineBuilder engine_builder; - const std::string region = "us-west-1"; - const std::string zone = "some_zone"; - const std::string sub_zone = "some_sub_zone"; - engine_builder.setNodeLocality(region, zone, sub_zone); - std::unique_ptr bootstrap = engine_builder.generateBootstrap(); - EXPECT_EQ(bootstrap->node().locality().region(), region); - EXPECT_EQ(bootstrap->node().locality().zone(), zone); - EXPECT_EQ(bootstrap->node().locality().sub_zone(), sub_zone); -} - -TEST(TestConfig, SetNodeMetadata) { - ProtobufWkt::Struct node_metadata; - (*node_metadata.mutable_fields())["string_field"].set_string_value("some_string"); - (*node_metadata.mutable_fields())["bool_field"].set_bool_value(true); - (*node_metadata.mutable_fields())["number_field"].set_number_value(3.14); - EngineBuilder engine_builder; - engine_builder.setNodeMetadata(node_metadata); - std::unique_ptr bootstrap = engine_builder.generateBootstrap(); - EXPECT_EQ(bootstrap->node().metadata().fields().at("string_field").string_value(), "some_string"); - EXPECT_EQ(bootstrap->node().metadata().fields().at("bool_field").bool_value(), true); - EXPECT_EQ(bootstrap->node().metadata().fields().at("number_field").number_value(), 3.14); -} - -#ifdef ENVOY_MOBILE_XDS -TEST(TestConfig, AddCdsLayer) { - XdsBuilder xds_builder(/*xds_server_address=*/"fake-xds-server", /*xds_server_port=*/12345); - xds_builder.addClusterDiscoveryService(); - EngineBuilder engine_builder; - engine_builder.setXds(std::move(xds_builder)); - - std::unique_ptr bootstrap = engine_builder.generateBootstrap(); - EXPECT_EQ(bootstrap->dynamic_resources().cds_resources_locator(), ""); - EXPECT_EQ(bootstrap->dynamic_resources().cds_config().initial_fetch_timeout().seconds(), - /*default_timeout=*/5); - - xds_builder = XdsBuilder(/*xds_server_address=*/"fake-xds-server", /*xds_server_port=*/12345); - const std::string cds_resources_locator = - "xdstp://traffic-director-global.xds.googleapis.com/envoy.config.cluster.v3.Cluster"; - const int timeout_seconds = 300; - xds_builder.addClusterDiscoveryService(cds_resources_locator, timeout_seconds); - engine_builder.setXds(std::move(xds_builder)); - bootstrap = engine_builder.generateBootstrap(); - EXPECT_EQ(bootstrap->dynamic_resources().cds_resources_locator(), cds_resources_locator); - EXPECT_EQ(bootstrap->dynamic_resources().cds_config().initial_fetch_timeout().seconds(), - timeout_seconds); - EXPECT_EQ(bootstrap->dynamic_resources().cds_config().api_config_source().api_type(), - envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC); - EXPECT_EQ(bootstrap->dynamic_resources().cds_config().api_config_source().transport_api_version(), - envoy::config::core::v3::ApiVersion::V3); -} -#endif - } // namespace } // namespace Envoy diff --git a/mobile/test/common/integration/BUILD b/mobile/test/common/integration/BUILD index e7d938ce874e..32251d8a6af5 100644 --- a/mobile/test/common/integration/BUILD +++ b/mobile/test/common/integration/BUILD @@ -3,7 +3,6 @@ load( "envoy_cc_test", "envoy_cc_test_library", "envoy_mobile_package", - "envoy_select_envoy_mobile_xds", "envoy_select_signal_trace", ) @@ -39,76 +38,6 @@ envoy_cc_test( ], ) -envoy_cc_test( - name = "rtds_integration_test", - srcs = envoy_select_envoy_mobile_xds( - ["rtds_integration_test.cc"], - "@envoy", - ), - data = [ - "@envoy//test/config/integration/certs", - ], - external_deps = [ - "abseil_strings", - ], - repository = "@envoy", - deps = [ - ":xds_integration_test_lib", - "@envoy//test/test_common:environment_lib", - "@envoy//test/test_common:test_runtime_lib", - "@envoy//test/test_common:utility_lib", - "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", - "@envoy_api//envoy/service/runtime/v3:pkg_cc_proto", - ], -) - -envoy_cc_test( - name = "cds_integration_test", - srcs = envoy_select_envoy_mobile_xds( - ["cds_integration_test.cc"], - "@envoy", - ), - data = [ - "@envoy//test/config/integration/certs", - ], - external_deps = [ - "abseil_strings", - ], - repository = "@envoy", - deps = [ - ":xds_integration_test_lib", - "@envoy//test/test_common:environment_lib", - "@envoy//test/test_common:utility_lib", - "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", - "@envoy_api//envoy/service/runtime/v3:pkg_cc_proto", - ], -) - -envoy_cc_test( - name = "sds_integration_test", - srcs = envoy_select_envoy_mobile_xds( - ["sds_integration_test.cc"], - "@envoy", - ), - data = [ - "@envoy//test/config/integration/certs", - ], - repository = "@envoy", - deps = [ - ":xds_integration_test_lib", - "@envoy//source/common/config:protobuf_link_hacks", - "@envoy//source/common/tls:context_config_lib", - "@envoy//source/common/tls:context_lib", - "@envoy//source/extensions/transport_sockets/tls:config", - "@envoy//test/test_common:environment_lib", - "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", - "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", - "@envoy_api//envoy/service/secret/v3:pkg_cc_proto", - ], -) - envoy_cc_test_library( name = "base_client_integration_test_lib", srcs = [ @@ -129,27 +58,6 @@ envoy_cc_test_library( ], ) -envoy_cc_test_library( - name = "xds_integration_test_lib", - srcs = [ - "xds_integration_test.cc", - ], - hdrs = [ - "xds_integration_test.h", - ], - repository = "@envoy", - deps = [ - ":base_client_integration_test_lib", - "@envoy//source/common/config:api_version_lib", - "@envoy//source/common/http:rds_lib", - "@envoy//test/test_common:environment_lib", - "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", - "@envoy_build_config//:extension_registry", - "@envoy_build_config//:test_extensions", - ], -) - # interface libs for test servers` jni implementations envoy_cc_test_library( name = "test_server_lib", @@ -192,35 +100,6 @@ envoy_cc_test_library( ], ) -envoy_cc_test_library( - name = "xds_test_server_lib", - srcs = [ - "xds_test_server.cc", - ], - hdrs = [ - "xds_test_server.h", - ], - repository = "@envoy", - deps = [ - ":base_client_integration_test_lib", - "@envoy//source/common/event:libevent_lib", - "@envoy//source/common/tls:context_config_lib", - "@envoy//source/common/tls:context_lib", - "@envoy//source/common/tls:ssl_socket_lib", - "@envoy//source/exe:process_wide_lib", - "@envoy//test/integration:autonomous_upstream_lib", - "@envoy//test/integration:utility_lib", - "@envoy//test/mocks/server:server_factory_context_mocks", - "@envoy//test/mocks/server:transport_socket_factory_context_mocks", - "@envoy//test/test_common:environment_lib", - "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", - "@envoy_build_config//:extension_registry", - ] + envoy_select_signal_trace( - ["@envoy//source/common/signal:sigaction_lib"], - "@envoy", - ), -) - envoy_cc_test_library( name = "engine_with_test_server", srcs = [ diff --git a/mobile/test/common/integration/cds_integration_test.cc b/mobile/test/common/integration/cds_integration_test.cc deleted file mode 100644 index 68f1d6efc137..000000000000 --- a/mobile/test/common/integration/cds_integration_test.cc +++ /dev/null @@ -1,128 +0,0 @@ -#include "envoy/config/bootstrap/v3/bootstrap.pb.h" -#include "envoy/service/runtime/v3/rtds.pb.h" - -#include "test/common/integration/xds_integration_test.h" -#include "test/test_common/environment.h" -#include "test/test_common/utility.h" - -#include "gtest/gtest.h" - -namespace Envoy { -namespace { - -using envoy::config::cluster::v3::Cluster; - -class CdsIntegrationTest : public XdsIntegrationTest { -public: - void initialize() override { - setUpstreamProtocol(Http::CodecType::HTTP1); - - XdsIntegrationTest::initialize(); - - default_request_headers_.setScheme("http"); - initializeXdsStream(); - } - - void createEnvoy() override { - sotw_or_delta_ = sotwOrDelta(); - const std::string target_uri = Network::Test::getLoopbackAddressUrlString(ipVersion()); - Platform::XdsBuilder xds_builder(target_uri, fake_upstreams_[1]->localAddress()->ip()->port()); - std::string cds_resources_locator; - if (use_xdstp_) { - cds_namespace_ = "xdstp://" + target_uri + "/envoy.config.cluster.v3.Cluster"; - cds_resources_locator = cds_namespace_ + "/*"; - } - xds_builder.addClusterDiscoveryService(cds_resources_locator, /*timeout_in_seconds=*/1) - .setSslRootCerts(getUpstreamCert()); - builder_.setXds(std::move(xds_builder)); - - XdsIntegrationTest::createEnvoy(); - } - - void SetUp() override { initialize(); } - -protected: - Cluster createCluster() { - const std::string cluster_name = - use_xdstp_ ? cds_namespace_ + "/my_cluster?xds.node.cluster=envoy-mobile" : "my_cluster"; - return ConfigHelper::buildStaticCluster(cluster_name, - fake_upstreams_[0]->localAddress()->ip()->port(), - Network::Test::getLoopbackAddressString(ipVersion())); - } - - std::vector getExpectedResources() { - std::vector expected_resources; - if (use_xdstp_) { - expected_resources.push_back(cds_namespace_ + "/*"); - } - return expected_resources; - } - - void sendInitialCdsResponseAndVerify(const std::string& version) { - const int cluster_count = getGaugeValue("cluster_manager.active_clusters"); - const std::vector expected_resources = getExpectedResources(); - - // Envoy sends the initial DiscoveryRequest. - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", expected_resources, {}, - {}, /*expect_node=*/true)); - - Cluster cluster = createCluster(); - // Server sends back the initial DiscoveryResponse. - sendDiscoveryResponse(Config::TypeUrl::get().Cluster, {cluster}, {cluster}, {}, - version); - - // Wait for cluster to be added. - EXPECT_TRUE(waitForCounterGe("cluster_manager.cluster_added", 1)); - EXPECT_TRUE(waitForGaugeGe("cluster_manager.active_clusters", cluster_count + 1)); - - // ACK of the initial version. - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, version, expected_resources, - {}, {}, /*expect_node=*/false)); - - EXPECT_TRUE(waitForGaugeGe("cluster_manager.cluster_removed", 0)); - } - - void sendUpdatedCdsResponseAndVerify(const std::string& version) { - const int cluster_count = getGaugeValue("cluster_manager.active_clusters"); - const std::vector expected_resources = getExpectedResources(); - - // Server sends an updated DiscoveryResponse over the xDS stream. - Cluster cluster = createCluster(); - sendDiscoveryResponse(Config::TypeUrl::get().Cluster, {cluster}, {cluster}, {}, - version); - - // ACK of the cluster update at the new version. - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, version, expected_resources, - {}, {}, /*expect_node=*/false)); - - // Cluster count should stay the same. - EXPECT_TRUE(waitForGaugeGe("cluster_manager.active_clusters", cluster_count)); - EXPECT_TRUE(waitForGaugeGe("cluster_manager.cluster_removed", 0)); - } - - bool use_xdstp_{false}; - std::string cds_namespace_; -}; - -INSTANTIATE_TEST_SUITE_P( - IpVersionsClientTypeSotw, CdsIntegrationTest, - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - testing::ValuesIn(TestEnvironment::getsGrpcVersionsForTest()), - // Envoy Mobile's xDS APIs only support state-of-the-world, not delta. - testing::Values(Grpc::SotwOrDelta::Sotw, Grpc::SotwOrDelta::UnifiedSotw))); - -TEST_P(CdsIntegrationTest, Basic) { sendInitialCdsResponseAndVerify(/*version=*/"55"); } - -TEST_P(CdsIntegrationTest, BasicWithXdstp) { - use_xdstp_ = true; - sendInitialCdsResponseAndVerify(/*version=*/"55"); -} - -TEST_P(CdsIntegrationTest, ClusterUpdates) { - use_xdstp_ = true; - sendInitialCdsResponseAndVerify(/*version=*/"55"); - sendUpdatedCdsResponseAndVerify(/*version=*/"56"); -} - -} // namespace -} // namespace Envoy diff --git a/mobile/test/common/integration/rtds_integration_test.cc b/mobile/test/common/integration/rtds_integration_test.cc deleted file mode 100644 index 8845abd36188..000000000000 --- a/mobile/test/common/integration/rtds_integration_test.cc +++ /dev/null @@ -1,115 +0,0 @@ -#include "envoy/config/bootstrap/v3/bootstrap.pb.h" -#include "envoy/service/runtime/v3/rtds.pb.h" - -#include "source/common/router/rds_impl.h" - -#include "test/common/integration/xds_integration_test.h" -#include "test/test_common/environment.h" -#include "test/test_common/test_runtime.h" -#include "test/test_common/utility.h" - -#include "gtest/gtest.h" - -namespace Envoy { -namespace { - -class RtdsIntegrationTest : public XdsIntegrationTest { -public: - void initialize() override { - // using http1 because the h1 cluster has a plaintext socket - setUpstreamProtocol(Http::CodecType::HTTP1); - - XdsIntegrationTest::initialize(); - Router::forceRegisterRdsFactoryImpl(); - - default_request_headers_.setScheme("http"); - initializeXdsStream(); - } - - void createEnvoy() override { - Platform::XdsBuilder xds_builder( - /*xds_server_address=*/Network::Test::getLoopbackAddressUrlString(ipVersion()), - /*xds_server_port=*/fake_upstreams_.back()->localAddress()->ip()->port()); - // Add the layered runtime config, which includes the RTDS layer. - xds_builder.addRuntimeDiscoveryService("some_rtds_resource", /*timeout_in_seconds=*/1) - .setSslRootCerts(getUpstreamCert()); - builder_.setXds(std::move(xds_builder)); - XdsIntegrationTest::createEnvoy(); - } - - void SetUp() override { initialize(); } - - void runReloadTest() { - stream_ = createNewStream(createDefaultStreamCallbacks()); - // Send a request on the data plane. - stream_->sendHeaders(std::make_unique(default_request_headers_), - true); - terminal_callback_.waitReady(); - EXPECT_EQ(cc_.on_headers_calls_, 1); - EXPECT_EQ(cc_.status_, "200"); - EXPECT_EQ(cc_.on_data_calls_, 2); - EXPECT_EQ(cc_.on_complete_calls_, 1); - EXPECT_EQ(cc_.on_cancel_calls_, 0); - EXPECT_EQ(cc_.on_error_calls_, 0); - EXPECT_EQ(cc_.on_header_consumed_bytes_from_response_, 27); - EXPECT_EQ(cc_.on_complete_received_byte_count_, 67); - // Check that the Runtime config is from the static layer. - EXPECT_FALSE(Runtime::runtimeFeatureEnabled("envoy.reloadable_features.test_feature_false")); - - const std::string load_success_counter = "runtime.load_success"; - uint64_t load_success_value = getCounterValue(load_success_counter); - // Send a RTDS request and get back the RTDS response. - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Runtime, "", {"some_rtds_resource"}, - {"some_rtds_resource"}, {}, true)); - - envoy::service::runtime::v3::Runtime some_rtds_resource; - some_rtds_resource.set_name("some_rtds_resource"); - auto* static_layer = some_rtds_resource.mutable_layer(); - (*static_layer->mutable_fields())["envoy.reloadable_features.test_feature_false"] - .set_bool_value(true); - - sendDiscoveryResponse( - Config::TypeUrl::get().Runtime, {some_rtds_resource}, {some_rtds_resource}, {}, "1"); - // Wait until the RTDS updates from the DiscoveryResponse have been applied. - ASSERT_TRUE(waitForCounterGe(load_success_counter, load_success_value + 1)); - - // Verify that the Runtime config values are from the RTDS response. - EXPECT_TRUE(Runtime::runtimeFeatureEnabled("envoy.reloadable_features.test_feature_false")); - - load_success_value = getCounterValue(load_success_counter); - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Runtime, "", {"some_rtds_resource"}, - {"some_rtds_resource"}, {})); - (*static_layer->mutable_fields())["envoy.reloadable_features.test_feature_false"] - .set_bool_value(false); - - // Send another response with Resource wrapper. - sendDiscoveryResponse( - Config::TypeUrl::get().Runtime, {some_rtds_resource}, {some_rtds_resource}, {}, "2", - {{"test", ProtobufWkt::Any()}}); - // Wait until the RTDS updates from the DiscoveryResponse have been applied. - ASSERT_TRUE(waitForCounterGe(load_success_counter, load_success_value + 1)); - - // Verify that the Runtime config values are from the RTDS response. - EXPECT_FALSE(Runtime::runtimeFeatureEnabled("envoy.reloadable_features.test_feature_false")); - } -}; - -INSTANTIATE_TEST_SUITE_P( - IpVersionsClientTypeDelta, RtdsIntegrationTest, - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - testing::ValuesIn(TestEnvironment::getsGrpcVersionsForTest()), - // Envoy Mobile's xDS APIs only support state-of-the-world, not delta. - testing::Values(Grpc::SotwOrDelta::Sotw, Grpc::SotwOrDelta::UnifiedSotw))); - -TEST_P(RtdsIntegrationTest, RtdsReloadWithDfpMixedScheme) { - TestScopedStaticReloadableFeaturesRuntime scoped_runtime({{"dfp_mixed_scheme", true}}); - runReloadTest(); -} - -TEST_P(RtdsIntegrationTest, RtdsReloadWithoutDfpMixedScheme) { - TestScopedStaticReloadableFeaturesRuntime scoped_runtime({{"dfp_mixed_scheme", false}}); - runReloadTest(); -} - -} // namespace -} // namespace Envoy diff --git a/mobile/test/common/integration/sds_integration_test.cc b/mobile/test/common/integration/sds_integration_test.cc deleted file mode 100644 index 4000d0f24e46..000000000000 --- a/mobile/test/common/integration/sds_integration_test.cc +++ /dev/null @@ -1,109 +0,0 @@ -#include "envoy/config/cluster/v3/cluster.pb.h" -#include "envoy/config/core/v3/config_source.pb.h" -#include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" -#include "envoy/service/secret/v3/sds.pb.h" - -#include "test/common/integration/xds_integration_test.h" -#include "test/integration/ssl_utility.h" -#include "test/test_common/environment.h" - -#include "gtest/gtest.h" - -namespace Envoy { -namespace { - -// Hack to force linking of the service: https://github.com/google/protobuf/issues/4221. -const envoy::service::secret::v3::SdsDummy _sds_dummy; -constexpr absl::string_view XDS_CLUSTER_NAME = "test_cluster"; -constexpr absl::string_view SECRET_NAME = "client_cert"; - -class SdsIntegrationTest : public XdsIntegrationTest { -public: - void initialize() override { - XdsIntegrationTest::initialize(); - initializeXdsStream(); - } - - void createEnvoy() override { - const std::string target_uri = Network::Test::getLoopbackAddressUrlString(ipVersion()); - Platform::XdsBuilder xds_builder(target_uri, - fake_upstreams_.back()->localAddress()->ip()->port()); - xds_builder.addClusterDiscoveryService().setSslRootCerts(getUpstreamCert()); - builder_.setXds(std::move(xds_builder)); - XdsIntegrationTest::createEnvoy(); - } - - void SetUp() override { initialize(); } - -protected: - void sendCdsResponse() { - auto cds_cluster = createSingleEndpointClusterConfig(std::string(XDS_CLUSTER_NAME)); - // Update the cluster to use SSL. - envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; - tls_context.set_sni("lyft.com"); - auto* secret_config = - tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs(); - setUpSdsConfig(secret_config, SECRET_NAME); - auto* transport_socket = cds_cluster.mutable_transport_socket(); - transport_socket->set_name("envoy.transport_sockets.tls"); - transport_socket->mutable_typed_config()->PackFrom(tls_context); - sendDiscoveryResponse( - Config::TypeUrl::get().Cluster, {cds_cluster}, {cds_cluster}, {}, "55"); - } - - void sendSdsResponse(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) { - envoy::service::discovery::v3::DiscoveryResponse discovery_response; - discovery_response.set_version_info("1"); - discovery_response.set_type_url(Config::TypeUrl::get().Secret); - discovery_response.add_resources()->PackFrom(secret); - xds_stream_->sendGrpcMessage(discovery_response); - } - - void setUpSdsConfig(envoy::extensions::transport_sockets::tls::v3::SdsSecretConfig* secret_config, - absl::string_view secret_name) { - secret_config->set_name(secret_name); - auto* config_source = secret_config->mutable_sds_config(); - config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); - // Envoy Mobile only supports SDS with ADS. - config_source->mutable_ads(); - config_source->mutable_initial_fetch_timeout()->set_seconds(5); - } - - static envoy::extensions::transport_sockets::tls::v3::Secret getClientSecret() { - envoy::extensions::transport_sockets::tls::v3::Secret secret; - secret.set_name(SECRET_NAME); - auto* tls_certificate = secret.mutable_tls_certificate(); - tls_certificate->mutable_certificate_chain()->set_filename( - TestEnvironment::runfilesPath("test/config/integration/certs/clientcert.pem")); - tls_certificate->mutable_private_key()->set_filename( - TestEnvironment::runfilesPath("test/config/integration/certs/clientkey.pem")); - return secret; - } -}; - -INSTANTIATE_TEST_SUITE_P( - IpVersionsClientTypeSotw, SdsIntegrationTest, - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - testing::ValuesIn(TestEnvironment::getsGrpcVersionsForTest()), - // Envoy Mobile's xDS APIs only support state-of-the-world, not delta. - testing::Values(Grpc::SotwOrDelta::Sotw, Grpc::SotwOrDelta::UnifiedSotw))); - -// Note: Envoy Mobile does not have listener sockets, so we aren't including a downstream test. -TEST_P(SdsIntegrationTest, SdsForUpstreamCluster) { - // Wait until the new cluster from CDS is added before sending the SDS response. - sendCdsResponse(); - ASSERT_TRUE(waitForCounterGe("cluster_manager.cluster_added", 1)); - - // Wait until the Envoy instance has obtained an updated secret from the SDS cluster. This - // verifies that the SDS API is working from the Envoy client and allows us to know we can start - // sending HTTP requests to the upstream cluster using the secret. - sendSdsResponse(getClientSecret()); - ASSERT_TRUE(waitForCounterGe(fmt::format("sds.{}.update_success", SECRET_NAME), 1)); - ASSERT_TRUE( - waitForCounterGe(fmt::format("cluster.{}.client_ssl_socket_factory.ssl_context_update_by_sds", - XDS_CLUSTER_NAME), - 1)); -} - -} // namespace -} // namespace Envoy diff --git a/mobile/test/common/integration/xds_integration_test.cc b/mobile/test/common/integration/xds_integration_test.cc deleted file mode 100644 index 605936636741..000000000000 --- a/mobile/test/common/integration/xds_integration_test.cc +++ /dev/null @@ -1,102 +0,0 @@ -#include "xds_integration_test.h" - -#include "envoy/config/bootstrap/v3/bootstrap.pb.h" -#include "envoy/config/cluster/v3/cluster.pb.h" - -#include "source/common/tls/server_context_impl.h" - -#include "test/common/grpc/grpc_client_integration.h" -#include "test/common/integration/base_client_integration_test.h" -#include "test/test_common/environment.h" -#include "test/test_common/utility.h" - -#include "extension_registry.h" -#include "gtest/gtest.h" - -namespace Envoy { - -using ::testing::AssertionFailure; -using ::testing::AssertionResult; -using ::testing::AssertionSuccess; - -XdsIntegrationTest::XdsIntegrationTest() : BaseClientIntegrationTest(ipVersion()) {} - -void XdsIntegrationTest::initialize() { - create_xds_upstream_ = true; - tls_xds_upstream_ = true; - sotw_or_delta_ = sotwOrDelta(); - - // Register the extensions required for Envoy Mobile. - ExtensionRegistry::registerFactories(); - // For server TLS. - Extensions::TransportSockets::Tls::forceRegisterServerContextFactoryImpl(); - - if (sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedSotw || - sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedDelta) { - config_helper_.addRuntimeOverride("envoy.reloadable_features.unified_mux", "true"); - } - - // xDS upstream is created separately in the test infra, and there's only one non-xDS cluster. - setUpstreamCount(1); - - BaseClientIntegrationTest::initialize(); - - default_request_headers_.setScheme("https"); -} - -Network::Address::IpVersion XdsIntegrationTest::ipVersion() const { - return std::get<0>(GetParam()); -} - -Grpc::ClientType XdsIntegrationTest::clientType() const { return std::get<1>(GetParam()); } - -Grpc::SotwOrDelta XdsIntegrationTest::sotwOrDelta() const { return std::get<2>(GetParam()); } - -void XdsIntegrationTest::SetUp() { - // TODO(abeyad): Add paramaterized tests for HTTP1, HTTP2, and HTTP3. - setUpstreamProtocol(Http::CodecType::HTTP2); -} - -void XdsIntegrationTest::createEnvoy() { - BaseClientIntegrationTest::createEnvoy(); - if (on_server_init_function_) { - on_server_init_function_(); - } -} - -void XdsIntegrationTest::initializeXdsStream() { - createXdsConnection(); - AssertionResult result = - xds_connection_->waitForNewStream(*BaseIntegrationTest::dispatcher_, xds_stream_); - RELEASE_ASSERT(result, result.message()); - xds_stream_->startGrpcStream(); -} - -envoy::config::cluster::v3::Cluster -XdsIntegrationTest::createSingleEndpointClusterConfig(const std::string& cluster_name) { - envoy::config::cluster::v3::Cluster config; - config.set_name(cluster_name); - - // Set the endpoint. - auto* load_assignment = config.mutable_load_assignment(); - load_assignment->set_cluster_name(cluster_name); - auto* endpoint = load_assignment->add_endpoints()->add_lb_endpoints()->mutable_endpoint(); - endpoint->mutable_address()->mutable_socket_address()->set_address( - Network::Test::getLoopbackAddressString(ipVersion())); - endpoint->mutable_address()->mutable_socket_address()->set_port_value(0); - - // Set the protocol options. - envoy::extensions::upstreams::http::v3::HttpProtocolOptions options; - options.mutable_explicit_http_config()->mutable_http2_protocol_options(); - (*config.mutable_typed_extension_protocol_options()) - ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] - .PackFrom(options); - return config; -} - -std::string XdsIntegrationTest::getUpstreamCert() { - return TestEnvironment::readFileToStringForTest( - TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem")); -} - -} // namespace Envoy diff --git a/mobile/test/common/integration/xds_integration_test.h b/mobile/test/common/integration/xds_integration_test.h deleted file mode 100644 index 701715b03b52..000000000000 --- a/mobile/test/common/integration/xds_integration_test.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include "envoy/config/bootstrap/v3/bootstrap.pb.h" -#include "envoy/config/cluster/v3/cluster.pb.h" - -#include "test/common/grpc/grpc_client_integration.h" -#include "test/common/integration/base_client_integration_test.h" - -#include "absl/strings/string_view.h" -#include "gtest/gtest.h" - -namespace Envoy { - -static constexpr absl::string_view XDS_CLUSTER = "xds_cluster.lyft.com"; - -// A base class for xDS integration tests. It provides common functionality for integration tests -// derived from BaseClientIntegrationTest that needs to communicate with upstream xDS servers. -class XdsIntegrationTest : public BaseClientIntegrationTest, - public Grpc::DeltaSotwIntegrationParamTest { -public: - XdsIntegrationTest(); - virtual ~XdsIntegrationTest() = default; - void initialize() override; - void TearDown() override { BaseClientIntegrationTest::TearDown(); } - -protected: - void SetUp() override; - - void createEnvoy() override; - - // Initializes the xDS connection and creates a gRPC bi-directional stream for receiving - // DiscoveryRequests and sending DiscoveryResponses. - void initializeXdsStream(); - - // Returns the IP version that the test is running with (IPv4 or IPv6). - Network::Address::IpVersion ipVersion() const override; - // Returns the gRPC client type that the test is running with (Envoy gRPC or Google gRPC). - Grpc::ClientType clientType() const override; - // Returns whether the test is using the state-of-the-world or Delta xDS protocol. - Grpc::SotwOrDelta sotwOrDelta() const; - - // Creates a cluster config with a single static endpoint, where the endpoint is intended to be of - // a fake upstream on the loopback address. - envoy::config::cluster::v3::Cluster - createSingleEndpointClusterConfig(const std::string& cluster_name); - - // Gets the upstream cert for the xDS cluster's TLS over the `base` cluster. - std::string getUpstreamCert(); -}; - -} // namespace Envoy diff --git a/mobile/test/common/integration/xds_test_server.cc b/mobile/test/common/integration/xds_test_server.cc deleted file mode 100644 index 55b42c654a01..000000000000 --- a/mobile/test/common/integration/xds_test_server.cc +++ /dev/null @@ -1,112 +0,0 @@ -#include "test/common/integration/xds_test_server.h" - -#include - -#include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" - -#include "source/common/event/libevent.h" -#include "source/common/tls/context_config_impl.h" -#include "source/common/tls/server_context_impl.h" -#include "source/common/tls/server_ssl_socket.h" -#include "source/common/tls/ssl_socket.h" -#include "source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.h" -#include "source/extensions/config_subscription/grpc/grpc_mux_impl.h" -#include "source/extensions/config_subscription/grpc/grpc_subscription_factory.h" -#include "source/extensions/config_subscription/grpc/new_grpc_mux_impl.h" - -#include "test/integration/fake_upstream.h" -#include "test/test_common/environment.h" -#include "test/test_common/network_utility.h" -#include "test/test_common/utility.h" - -namespace Envoy { - -XdsTestServer::XdsTestServer() - : api_(Api::createApiForTest(stats_store_, time_system_)), - version_(Network::Address::IpVersion::v4), - mock_buffer_factory_(new NiceMock), upstream_config_(time_system_) { - std::string runfiles_error; - runfiles_ = std::unique_ptr{ - bazel::tools::cpp::runfiles::Runfiles::Create("", &runfiles_error)}; - RELEASE_ASSERT(TestEnvironment::getOptionalEnvVar("NORUNFILES").has_value() || - runfiles_ != nullptr, - runfiles_error); - TestEnvironment::setRunfiles(runfiles_.get()); - - if (!Envoy::Event::Libevent::Global::initialized()) { - // Required by the Dispatcher. - Envoy::Event::Libevent::Global::initialize(); - } - dispatcher_ = - api_->allocateDispatcher("test_thread", Buffer::WatermarkFactoryPtr{mock_buffer_factory_}); - - ON_CALL(*mock_buffer_factory_, createBuffer_(_, _, _)) - .WillByDefault(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), - std::move(above_overflow)); - })); - ON_CALL(factory_context_.server_context_, api()).WillByDefault(testing::ReturnRef(*api_)); - ON_CALL(factory_context_, statsScope()) - .WillByDefault(testing::ReturnRef(*stats_store_.rootScope())); - Logger::Context logging_state(spdlog::level::level_enum::err, - "[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v", lock_, false, false); - upstream_config_.upstream_protocol_ = Http::CodecType::HTTP2; - Extensions::TransportSockets::Tls::forceRegisterServerContextFactoryImpl(); - Config::forceRegisterAdsConfigSubscriptionFactory(); - Config::forceRegisterGrpcConfigSubscriptionFactory(); - Config::forceRegisterDeltaGrpcConfigSubscriptionFactory(); - Config::forceRegisterDeltaGrpcCollectionConfigSubscriptionFactory(); - Config::forceRegisterAggregatedGrpcCollectionConfigSubscriptionFactory(); - Config::forceRegisterAdsCollectionConfigSubscriptionFactory(); - Config::forceRegisterGrpcMuxFactory(); - Config::forceRegisterNewGrpcMuxFactory(); - - envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context; - auto* common_tls_context = tls_context.mutable_common_tls_context(); - common_tls_context->add_alpn_protocols(Http::Utility::AlpnNames::get().Http2); - auto* tls_cert = common_tls_context->add_tls_certificates(); - tls_cert->mutable_certificate_chain()->set_filename( - TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcert.pem")); - tls_cert->mutable_private_key()->set_filename( - TestEnvironment::runfilesPath("test/config/integration/certs/upstreamkey.pem")); - auto cfg = *Extensions::TransportSockets::Tls::ServerContextConfigImpl::create(tls_context, - factory_context_); - auto context = *Extensions::TransportSockets::Tls::ServerSslSocketFactory::create( - std::move(cfg), context_manager_, *stats_store_.rootScope(), std::vector{}); - xds_upstream_ = std::make_unique(std::move(context), 0, version_, upstream_config_); -} - -std::string XdsTestServer::getHost() const { - return Network::Test::getLoopbackAddressUrlString(version_); -} - -int XdsTestServer::getPort() const { - ASSERT(xds_upstream_); - return xds_upstream_->localAddress()->ip()->port(); -} - -void XdsTestServer::start() { - AssertionResult result = xds_upstream_->waitForHttpConnection(*dispatcher_, xds_connection_); - RELEASE_ASSERT(result, result.message()); - result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); - RELEASE_ASSERT(result, result.message()); - xds_stream_->startGrpcStream(); -} - -void XdsTestServer::send(const envoy::service::discovery::v3::DiscoveryResponse& response) { - ASSERT(xds_stream_); - xds_stream_->sendGrpcMessage(response); -} - -void XdsTestServer::shutdown() { - if (xds_connection_ != nullptr) { - AssertionResult result = xds_connection_->close(); - RELEASE_ASSERT(result, result.message()); - result = xds_connection_->waitForDisconnect(); - RELEASE_ASSERT(result, result.message()); - xds_connection_.reset(); - } -} - -} // namespace Envoy diff --git a/mobile/test/common/integration/xds_test_server.h b/mobile/test/common/integration/xds_test_server.h deleted file mode 100644 index f00bc8cb4030..000000000000 --- a/mobile/test/common/integration/xds_test_server.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "envoy/api/api.h" - -#include "source/common/stats/isolated_store_impl.h" -#include "source/common/tls/context_manager_impl.h" - -#include "test/integration/fake_upstream.h" -#include "test/integration/server.h" -#include "test/mocks/server/server_factory_context.h" -#include "test/mocks/server/transport_socket_factory_context.h" -#include "test/test_common/test_time.h" - -#include "tools/cpp/runfiles/runfiles.h" - -namespace Envoy { - -/** An xDS test server. */ -class XdsTestServer { -public: - XdsTestServer(); - - /** Starts the xDS server and returns the port number of the server. */ - void start(); - - /** Gets the xDS host. */ - std::string getHost() const; - - /** Gets the xDS port. */ - int getPort() const; - - /** Sends a `DiscoveryResponse` from the xDS server. */ - void send(const envoy::service::discovery::v3::DiscoveryResponse& response); - - /** Shuts down the xDS server. */ - void shutdown(); - -private: - testing::NiceMock factory_context_; - testing::NiceMock server_factory_context_; - Stats::IsolatedStoreImpl stats_store_; - Event::GlobalTimeSystem time_system_; - Api::ApiPtr api_; - Network::Address::IpVersion version_; - MockBufferFactory* mock_buffer_factory_; - Event::DispatcherPtr dispatcher_; - FakeUpstreamConfig upstream_config_; - Thread::MutexBasicLockable lock_; - Extensions::TransportSockets::Tls::ContextManagerImpl context_manager_{server_factory_context_}; - std::unique_ptr runfiles_; - std::unique_ptr xds_upstream_; - FakeHttpConnectionPtr xds_connection_; - FakeStreamPtr xds_stream_; -}; - -} // namespace Envoy diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt index 814634299386..d31ff159b2ad 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt @@ -104,20 +104,6 @@ class EnvoyConfigurationTest { runtimeGuards: Map = emptyMap(), enablePlatformCertificatesValidation: Boolean = false, upstreamTlsSni: String = "", - rtdsResourceName: String = "", - rtdsTimeoutSeconds: Int = 0, - xdsAddress: String = "", - xdsPort: Int = 0, - xdsGrpcInitialMetadata: Map = emptyMap(), - xdsSslRootCerts: String = "", - nodeId: String = "", - nodeRegion: String = "", - nodeZone: String = "", - nodeSubZone: String = "", - nodeMetadata: Struct = Struct.getDefaultInstance(), - cdsResourcesLocator: String = "", - cdsTimeoutSeconds: Int = 0, - enableCds: Boolean = false, ): EnvoyConfiguration { return EnvoyConfiguration( @@ -158,20 +144,6 @@ class EnvoyConfigurationTest { runtimeGuards, enablePlatformCertificatesValidation, upstreamTlsSni, - rtdsResourceName, - rtdsTimeoutSeconds, - xdsAddress, - xdsPort, - xdsGrpcInitialMetadata, - xdsSslRootCerts, - nodeId, - nodeRegion, - nodeZone, - nodeSubZone, - nodeMetadata, - cdsResourcesLocator, - cdsTimeoutSeconds, - enableCds ) } @@ -302,11 +274,6 @@ class EnvoyConfigurationTest { // enablePlatformCertificatesValidation = true assertThat(resolvedTemplate).doesNotContain("trusted_ca") - - // ADS and RTDS not included by default - assertThat(resolvedTemplate).doesNotContain("rtds_layer"); - assertThat(resolvedTemplate).doesNotContain("ads_config"); - assertThat(resolvedTemplate).doesNotContain("cds_config"); } @Test @@ -321,100 +288,4 @@ class EnvoyConfigurationTest { assertThat(resolvedTemplate).contains("test_feature_false") assertThat(resolvedTemplate).contains("test_feature_true") } - - @Test - fun `test adding RTDS`() { - if (isEnvoyMobileXdsDisabled()) { - return - } - - JniLibrary.loadTestLibrary() - val envoyConfiguration = buildTestEnvoyConfiguration( - rtdsResourceName = "fake_rtds_layer", rtdsTimeoutSeconds = 5432, xdsAddress = "FAKE_ADDRESS", xdsPort = 0 - ) - - val resolvedTemplate = TestJni.createProtoString(envoyConfiguration) - assertThat(resolvedTemplate).contains("fake_rtds_layer"); - assertThat(resolvedTemplate).contains("FAKE_ADDRESS"); - assertThat(resolvedTemplate).contains("initial_fetch_timeout { seconds: 5432 }") - } - - @Test - fun `test adding RTDS and CDS`() { - if (isEnvoyMobileXdsDisabled()) { - return - } - - JniLibrary.loadTestLibrary() - val envoyConfiguration = buildTestEnvoyConfiguration( - cdsResourcesLocator = "FAKE_CDS_LOCATOR", cdsTimeoutSeconds = 356, xdsAddress = "FAKE_ADDRESS", xdsPort = 0, enableCds = true - ) - - val resolvedTemplate = TestJni.createProtoString(envoyConfiguration) - - assertThat(resolvedTemplate).contains("FAKE_CDS_LOCATOR"); - assertThat(resolvedTemplate).contains("FAKE_ADDRESS"); - assertThat(resolvedTemplate).contains("initial_fetch_timeout { seconds: 356 }") - } - - @Test - fun `test not using enableCds`() { - JniLibrary.loadTestLibrary() - val envoyConfiguration = buildTestEnvoyConfiguration( - cdsResourcesLocator = "FAKE_CDS_LOCATOR", cdsTimeoutSeconds = 123456, xdsAddress = "FAKE_ADDRESS", xdsPort = 0 - ) - - val resolvedTemplate = TestJni.createProtoString(envoyConfiguration) - - assertThat(resolvedTemplate).doesNotContain("FAKE_CDS_LOCATOR"); - assertThat(resolvedTemplate).doesNotContain("1234356"); - } - - @Test - fun `test enableCds with default string`() { - if (isEnvoyMobileXdsDisabled()) { - return - } - - JniLibrary.loadTestLibrary() - val envoyConfiguration = buildTestEnvoyConfiguration( - enableCds = true, xdsAddress = "FAKE_ADDRESS", xdsPort = 0 - ) - - val resolvedTemplate = TestJni.createProtoString(envoyConfiguration) - - assertThat(resolvedTemplate).contains("cds_config"); - assertThat(resolvedTemplate).contains("initial_fetch_timeout { seconds: 5 }") - } - - @Test - fun `test RTDS default timeout`() { - if (isEnvoyMobileXdsDisabled()) { - return - } - - JniLibrary.loadTestLibrary() - val envoyConfiguration = buildTestEnvoyConfiguration( - rtdsResourceName = "fake_rtds_layer", xdsAddress = "FAKE_ADDRESS", xdsPort = 0 - ) - - val resolvedTemplate = TestJni.createProtoString(envoyConfiguration) - - assertThat(resolvedTemplate).contains("initial_fetch_timeout { seconds: 5 }") - } - - @Test - fun `test node metadata`() { - JniLibrary.loadTestLibrary() - val envoyConfiguration = buildTestEnvoyConfiguration( - nodeMetadata = Struct.newBuilder() - .putFields("metadata_field", Value.newBuilder().setStringValue("metadata_value").build()) - .build() - ) - - val resolvedTemplate = TestJni.createProtoString(envoyConfiguration) - - assertThat(resolvedTemplate).contains("metadata_field") - assertThat(resolvedTemplate).contains("metadata_value") - } } diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD b/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD index fb431ad3148f..fcf758ad7eaf 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD @@ -53,22 +53,6 @@ android_library( ], ) -android_library( - name = "xds_test_server_factory_lib", - testonly = True, - srcs = [ - "XdsTestServerFactory.java", - ], - data = [ - "//test/jni:libenvoy_jni_xds_test_server_factory.so", - ], - visibility = ["//visibility:public"], - deps = [ - "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", - "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", - ], -) - envoy_mobile_android_test( name = "quic_test_server_test", srcs = [ diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/XdsTestServerFactory.java b/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/XdsTestServerFactory.java deleted file mode 100644 index 140134932a66..000000000000 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/XdsTestServerFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.envoyproxy.envoymobile.engine.testing; - -/** An xDS test server factory. */ -public class XdsTestServerFactory { - /** The instance of {@link XdsTestServer}. */ - public static class XdsTestServer { - private final long handle; // Used by the native code. - private final String host; - private final int port; - - private XdsTestServer(long handle, String host, int port) { - this.handle = handle; - this.host = host; - this.port = port; - } - - /** Starts the xDS server. */ - public native void start(); - - /** Gets the xDS host. */ - public String getHost() { return host; } - - /** Gets the xDS port. */ - public int getPort() { return port; } - - /** - * Sends a static `envoy::service::discovery::v3::DiscoveryResponse` message with the specified - * cluster name. - * - * TODO(fredyw): Update to take a DiscoveryResponse proto. - */ - public native void sendDiscoveryResponse(String clusterName); - - /** Shuts down the xDS server. */ - public native void shutdown(); - } - - static { System.loadLibrary("envoy_jni_xds_test_server_factory"); } - - /** Creates a new instance of {@link XdsTestServer}. */ - public static native XdsTestServer create(); -} diff --git a/mobile/test/jni/BUILD b/mobile/test/jni/BUILD index 22afd615eef8..ff743ed10201 100644 --- a/mobile/test/jni/BUILD +++ b/mobile/test/jni/BUILD @@ -103,40 +103,6 @@ cc_binary( ], ) -# Library which contains JNI functions for the XdsTestServer. -cc_library( - name = "jni_xds_test_server_factory_lib", - testonly = True, - srcs = [ - "jni_xds_test_server_factory.cc", - ], - linkopts = select({ - "@envoy//bazel:dbg_build": ["-Wl,--build-id=sha1"], - "//conditions:default": [], - }), - deps = [ - "//library/jni:jni_helper_lib", - "//library/jni:jni_utility_lib", - "//test/common/integration:xds_test_server_lib", - "@envoy//test/test_common:test_version_linkstamp", - "@envoy_build_config//:extension_registry", - # This is needed since some tests use test extensions. - "@envoy_build_config//:test_extensions", - ], - # We need this to ensure that we link this into the .so even though there are no code references. - alwayslink = True, -) - -cc_binary( - name = "libenvoy_jni_xds_test_server_factory.so", - testonly = True, - linkshared = True, - deps = [ - ":jni_xds_test_server_factory_lib", - "@envoy_mobile_extra_jni_deps//:extra_jni_dep", - ], -) - # Base binary (.so) for testing cc_binary( name = "libenvoy_jni_with_test_extensions.so", diff --git a/mobile/test/jni/jni_xds_test_server_factory.cc b/mobile/test/jni/jni_xds_test_server_factory.cc deleted file mode 100644 index 50f587d20cc3..000000000000 --- a/mobile/test/jni/jni_xds_test_server_factory.cc +++ /dev/null @@ -1,81 +0,0 @@ -#include - -#include "test/common/integration/xds_test_server.h" - -#include "extension_registry.h" -#include "library/jni/jni_helper.h" -#include "library/jni/jni_utility.h" - -// NOLINT(namespace-envoy) - -extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { - Envoy::JNI::JniHelper::initialize(vm); - Envoy::JNI::JniHelper::addClassToCache("java/util/Map$Entry"); - Envoy::JNI::JniHelper::addClassToCache( - "io/envoyproxy/envoymobile/engine/testing/XdsTestServerFactory$XdsTestServer"); - return Envoy::JNI::JniHelper::getVersion(); -} - -extern "C" JNIEXPORT jobject JNICALL -Java_io_envoyproxy_envoymobile_engine_testing_XdsTestServerFactory_create(JNIEnv* env, jclass) { - // This is called via JNI from kotlin tests, and Envoy doesn't consider it a test thread - // which triggers some failures of `ASSERT_IS_MAIN_OR_TEST_THREAD()`. - Envoy::Thread::SkipAsserts skip; - Envoy::JNI::JniHelper jni_helper(env); - - Envoy::ExtensionRegistry::registerFactories(); - Envoy::XdsTestServer* test_server = new Envoy::XdsTestServer(); - - jclass java_xds_server_factory_class = jni_helper.findClass( - "io/envoyproxy/envoymobile/engine/testing/XdsTestServerFactory$XdsTestServer"); - auto java_init_method_id = - jni_helper.getMethodId(java_xds_server_factory_class, "", "(JLjava/lang/String;I)V"); - auto host = Envoy::JNI::cppStringToJavaString(jni_helper, test_server->getHost()); - jint port = static_cast(test_server->getPort()); - return jni_helper - .newObject(java_xds_server_factory_class, java_init_method_id, - reinterpret_cast(test_server), host.get(), port) - .release(); -} - -extern "C" JNIEXPORT void JNICALL -Java_io_envoyproxy_envoymobile_engine_testing_XdsTestServerFactory_00024XdsTestServer_start( - JNIEnv* env, jobject instance) { - Envoy::JNI::JniHelper jni_helper(env); - auto java_class = jni_helper.getObjectClass(instance); - auto java_handle_field_id = jni_helper.getFieldId(java_class.get(), "handle", "J"); - jlong java_handle = jni_helper.getLongField(instance, java_handle_field_id); - Envoy::XdsTestServer* test_server = reinterpret_cast(java_handle); - test_server->start(); -} - -extern "C" JNIEXPORT void JNICALL -Java_io_envoyproxy_envoymobile_engine_testing_XdsTestServerFactory_00024XdsTestServer_sendDiscoveryResponse( - JNIEnv* env, jobject instance, jstring cluster_name) { - Envoy::JNI::JniHelper jni_helper(env); - auto java_class = jni_helper.getObjectClass(instance); - auto java_handle_field_id = jni_helper.getFieldId(java_class.get(), "handle", "J"); - jlong java_handle = jni_helper.getLongField(instance, java_handle_field_id); - Envoy::XdsTestServer* test_server = reinterpret_cast(java_handle); - - envoy::service::discovery::v3::DiscoveryResponse response; - *response.mutable_version_info() = "v1"; - envoy::config::cluster::v3::Cluster cluster; - *cluster.mutable_name() = Envoy::JNI::javaStringToCppString(jni_helper, cluster_name); - response.add_resources()->PackFrom(cluster); - *response.mutable_type_url() = "type.googleapis.com/envoy.config.cluster.v3.Cluster"; - - test_server->send(response); -} - -extern "C" JNIEXPORT void JNICALL -Java_io_envoyproxy_envoymobile_engine_testing_XdsTestServerFactory_00024XdsTestServer_shutdown( - JNIEnv* env, jobject instance) { - Envoy::JNI::JniHelper jni_helper(env); - auto java_class = jni_helper.getObjectClass(instance); - auto java_handle_field_id = jni_helper.getFieldId(java_class.get(), "handle", "J"); - jlong java_handle = jni_helper.getLongField(instance, java_handle_field_id); - Envoy::XdsTestServer* test_server = reinterpret_cast(java_handle); - test_server->shutdown(); - delete test_server; -} diff --git a/mobile/test/kotlin/integration/BUILD b/mobile/test/kotlin/integration/BUILD index 2c376e6322aa..377fe60fe53d 100644 --- a/mobile/test/kotlin/integration/BUILD +++ b/mobile/test/kotlin/integration/BUILD @@ -349,28 +349,6 @@ envoy_mobile_android_test( ], ) -envoy_mobile_android_test( - name = "xds_test", - srcs = [ - "XdsTest.kt", - ], - native_deps = [ - "@envoy//test/config/integration/certs", - "//test/jni:libenvoy_jni_with_test_extensions.so", - ] + select({ - "@platforms//os:macos": [ - "//test/jni:libenvoy_jni_with_test_extensions_jnilib", - ], - "//conditions:default": [], - }), - native_lib_name = "envoy_jni_with_test_extensions", - deps = [ - ":test_utilities", - "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", - "//test/java/io/envoyproxy/envoymobile/engine/testing:xds_test_server_factory_lib", - ], -) - kt_android_library( name = "test_utilities", srcs = [ diff --git a/mobile/test/kotlin/integration/EngineApiTest.kt b/mobile/test/kotlin/integration/EngineApiTest.kt index f5e9a62ba45f..b837152a5257 100644 --- a/mobile/test/kotlin/integration/EngineApiTest.kt +++ b/mobile/test/kotlin/integration/EngineApiTest.kt @@ -1,10 +1,6 @@ package test.kotlin.integration import com.google.common.truth.Truth.assertThat -import com.google.protobuf.NullValue -import com.google.protobuf.Struct -import com.google.protobuf.Value -import io.envoyproxy.envoymobile.Element import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.LogLevel import io.envoyproxy.envoymobile.engine.JniLibrary @@ -27,38 +23,11 @@ class EngineApiTest { EngineBuilder() .setLogLevel(LogLevel.DEBUG) .setLogger { _, msg -> print(msg) } - .setNodeId("node-id") - .setNodeLocality("region", "zone", "subzone") - .setNodeMetadata( - Struct.newBuilder() - .putFields("string_value", Value.newBuilder().setStringValue("string").build()) - .putFields("number_value", Value.newBuilder().setNumberValue(123.0).build()) - .putFields("bool_value", Value.newBuilder().setBoolValue(true).build()) - .putFields("null_value", Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) - .putFields( - "struct_value", - Value.newBuilder() - .setStructValue( - Struct.newBuilder() - .putFields( - "nested_value", - Value.newBuilder().setStringValue("nested_string").build() - ) - .build() - ) - .build() - ) - .build() - ) .setOnEngineRunning { countDownLatch.countDown() } .build() assertThat(countDownLatch.await(30, TimeUnit.SECONDS)).isTrue() - engine.pulseClient().counter(Element("foo"), Element("bar")).increment(1) - - assertThat(engine.dumpStats()).contains("pulse.foo.bar: 1") - engine.terminate() } } diff --git a/mobile/test/kotlin/integration/XdsTest.kt b/mobile/test/kotlin/integration/XdsTest.kt deleted file mode 100644 index 851babc81c3a..000000000000 --- a/mobile/test/kotlin/integration/XdsTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package test.kotlin.integration - -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import io.envoyproxy.envoymobile.AndroidEngineBuilder -import io.envoyproxy.envoymobile.Engine -import io.envoyproxy.envoymobile.LogLevel -import io.envoyproxy.envoymobile.XdsBuilder -import io.envoyproxy.envoymobile.engine.JniLibrary -import io.envoyproxy.envoymobile.engine.testing.XdsTestServerFactory -import java.io.File -import java.util.concurrent.CountDownLatch -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner - -@RunWith(RobolectricTestRunner::class) -class XdsTest { - private val appContext: Context = ApplicationProvider.getApplicationContext() - private lateinit var engine: Engine - - init { - JniLibrary.loadTestLibrary() - } - - private lateinit var xdsTestServer: XdsTestServerFactory.XdsTestServer - - @Before - fun setUp() { - val upstreamCert: String = - File("../envoy/test/config/integration/certs/upstreamcacert.pem").readText() - xdsTestServer = XdsTestServerFactory.create() - val latch = CountDownLatch(1) - engine = - AndroidEngineBuilder(appContext) - .setLogLevel(LogLevel.DEBUG) - .setLogger { _, msg -> print(msg) } - .setOnEngineRunning { latch.countDown() } - .setXds( - XdsBuilder( - xdsTestServer.host, - xdsTestServer.port, - ) - .setSslRootCerts(upstreamCert) - .addClusterDiscoveryService() - ) - .build() - latch.await() - xdsTestServer.start() - } - - @After - fun tearDown() { - engine.terminate() - xdsTestServer.shutdown() - } - - @Test - fun `test xDS with CDS`() { - // There are 2 initial clusters: base and base_clear. - engine.waitForStatGe("cluster_manager.cluster_added", 2) - xdsTestServer.sendDiscoveryResponse("my_cluster") - // There are now 3 clusters: base, base_cluster, and my_cluster. - engine.waitForStatGe("cluster_manager.cluster_added", 3) - } -} diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt index a8b841f2777b..2af872db8703 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt @@ -188,31 +188,6 @@ class EngineBuilderTest { assertThat(engine.envoyConfiguration.nativeFilterChain.size).isEqualTo(1) } - @Test - fun `specifying xDS works`() { - var xdsBuilder = XdsBuilder("fake_test_address", 0) - xdsBuilder - .addInitialStreamHeader("x-goog-api-key", "A1B2C3") - .addInitialStreamHeader("x-android-package", "com.google.myapp") - xdsBuilder.setSslRootCerts("my_root_certs") - xdsBuilder.addRuntimeDiscoveryService("some_rtds_resource") - xdsBuilder.addClusterDiscoveryService( - "xdstp://fake_test_address/envoy.config.cluster.v3.Cluster/xyz" - ) - engineBuilder = EngineBuilder() - engineBuilder.addEngineType { envoyEngine } - engineBuilder.setXds(xdsBuilder) - - val engine = engineBuilder.build() as EngineImpl - assertThat(engine.envoyConfiguration.xdsAddress).isEqualTo("fake_test_address") - assertThat(engine.envoyConfiguration.xdsGrpcInitialMetadata) - .isEqualTo(mapOf("x-goog-api-key" to "A1B2C3", "x-android-package" to "com.google.myapp")) - assertThat(engine.envoyConfiguration.xdsRootCerts).isEqualTo("my_root_certs") - assertThat(engine.envoyConfiguration.rtdsResourceName).isEqualTo("some_rtds_resource") - assertThat(engine.envoyConfiguration.cdsResourcesLocator) - .isEqualTo("xdstp://fake_test_address/envoy.config.cluster.v3.Cluster/xyz") - } - @Test fun `specifying runtime guards work`() { engineBuilder = EngineBuilder() diff --git a/mobile/test/non_hermetic/BUILD b/mobile/test/non_hermetic/BUILD deleted file mode 100644 index 171aa182617b..000000000000 --- a/mobile/test/non_hermetic/BUILD +++ /dev/null @@ -1,47 +0,0 @@ -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_test_binary", - "envoy_mobile_package", - "envoy_select_envoy_mobile_xds", -) - -licenses(["notice"]) # Apache 2 - -envoy_mobile_package() - -# This is a envoy_cc_test_binary instead of an envoy_cc_test because we don't want it to be run -# when `bazel test //test/...` is called. -envoy_cc_test_binary( - name = "gcp_traffic_director_integration_test", - srcs = envoy_select_envoy_mobile_xds( - ["gcp_traffic_director_integration_test.cc"], - "@envoy", - ), - data = [ - "@envoy//test/config/integration/certs", - ], - external_deps = [ - "abseil_strings", - ], - repository = "@envoy", - deps = [ - "//library/common:internal_engine_lib_no_stamp", - "//library/common/bridge:utility_lib", - "//library/common/types:c_types_lib", - "//test/common/integration:base_client_integration_test_lib", - "@envoy//source/common/config:api_version_lib", - "@envoy//source/common/protobuf:utility_lib_header", - "@envoy//source/extensions/clusters/strict_dns:strict_dns_cluster_lib", - "@envoy//source/extensions/health_checkers/http:health_checker_lib", - "@envoy//source/extensions/load_balancing_policies/round_robin:config", - "@envoy//test/common/grpc:grpc_client_integration_lib", - "@envoy//test/integration:http_integration_lib", - "@envoy//test/test_common:environment_lib", - "@envoy//test/test_common:network_utility_lib", - "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", - "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - "@envoy_build_config//:extension_registry", - "@envoy_build_config//:test_extensions", - ], -) diff --git a/mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc b/mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc deleted file mode 100644 index 8bbc367a1fcc..000000000000 --- a/mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc +++ /dev/null @@ -1,139 +0,0 @@ -// This test is not meant to be run on the command line, because it depends on a GCP -// authentication token provided as a GitHub encrypted secret through a GitHub actions workflow. - -#include -#include -#include - -#include "envoy/config/bootstrap/v3/bootstrap.pb.h" -#include "envoy/config/cluster/v3/cluster.pb.h" -#include "envoy/config/core/v3/base.pb.h" -#include "envoy/config/core/v3/config_source.pb.h" - -#include "source/common/protobuf/utility.h" -#include "source/extensions/clusters/strict_dns/strict_dns_cluster.h" -#include "source/extensions/health_checkers/http/health_checker_impl.h" -#include "source/extensions/load_balancing_policies/round_robin/config.h" - -#include "test/common/grpc/grpc_client_integration.h" -#include "test/common/integration/base_client_integration_test.h" -#include "test/test_common/environment.h" - -#include "absl/strings/substitute.h" -#include "absl/synchronization/notification.h" -#include "extension_registry.h" -#include "gtest/gtest.h" -#include "library/common/data/utility.h" -#include "library/common/types/c_types.h" -#include "tools/cpp/runfiles/runfiles.h" - -namespace Envoy { -namespace { - -using ::Envoy::Grpc::SotwOrDelta; -using ::Envoy::Network::Address::IpVersion; - -// The One-Platform API endpoint for Traffic Director. -constexpr char TD_API_ENDPOINT[] = "trafficdirectorconsumermesh.googleapis.com"; -// The project number of the project, found on the main page of the project in -// Google Cloud Console. -constexpr char PROJECT_ID[] = "33303528656"; - -// Tests that Envoy Mobile can connect to Traffic Director (an xDS management server offered by GCP) -// via a test GCP project, and can pull down xDS config for the given project. -class GcpTrafficDirectorIntegrationTest - : public BaseClientIntegrationTest, - public testing::TestWithParam> { -public: - GcpTrafficDirectorIntegrationTest() : BaseClientIntegrationTest(ip_version()) { - // TODO(https://github.com/envoyproxy/envoy/issues/27848): remove these force registrations - // once the EngineBuilder APIs support conditional force registration. - - // Register the extensions required for Envoy Mobile. - ExtensionRegistry::registerFactories(); - - // Force register the cluster factories used by the test. - Upstream::forceRegisterStrictDnsClusterFactory(); - Upstream::forceRegisterHttpHealthCheckerFactory(); - Extensions::LoadBalancingPolices::RoundRobin::forceRegisterFactory(); - - std::string root_certs(TestEnvironment::readFileToStringForTest( - TestEnvironment::runfilesPath("test/config/integration/certs/google_root_certs.pem"))); - - // API key for the `bct-prod-td-consumer-mesh` GCP test project. - const char* api_key = std::getenv("GCP_TEST_PROJECT_PROD_API_KEY"); - RELEASE_ASSERT(api_key != nullptr, - "GCP_TEST_PROJECT_PROD_API_KEY environment variable not set."); - - Platform::XdsBuilder xds_builder(/*xds_server_address=*/std::string(TD_API_ENDPOINT), - /*xds_server_port=*/443); - xds_builder.addInitialStreamHeader("x-goog-api-key", std::string(api_key)) - .setSslRootCerts(std::move(root_certs)) - .addClusterDiscoveryService(); - builder_.setLogLevel(Logger::Logger::Levels::trace) - .setNodeId(absl::Substitute("projects/$0/networks/default/nodes/111222333444", PROJECT_ID)) - .setXds(std::move(xds_builder)); - - // Other test knobs. - skip_tag_extraction_rule_check_ = true; - // Envoy Mobile does not use LDS. - use_lds_ = false; - // We don't need a fake xDS upstream since we are using Traffic Director. - create_xds_upstream_ = false; - sotw_or_delta_ = api_type(); - - if (api_type() == SotwOrDelta::UnifiedSotw || api_type() == SotwOrDelta::UnifiedDelta) { - config_helper_.addRuntimeOverride("envoy.reloadable_features.unified_mux", "true"); - } - } - - void TearDown() override { BaseClientIntegrationTest::TearDown(); } - - IpVersion ip_version() const { return std::get<0>(GetParam()); } - SotwOrDelta api_type() const { return std::get<1>(GetParam()); } -}; - -INSTANTIATE_TEST_SUITE_P( - GrpcOptions, GcpTrafficDirectorIntegrationTest, - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - testing::Values(SotwOrDelta::Sotw, SotwOrDelta::UnifiedSotw))); - -TEST_P(GcpTrafficDirectorIntegrationTest, AdsDynamicClusters) { - // Starts up Envoy and loads the bootstrap config, which will trigger fetching - // of the dynamic cluster resources from Traffic Director. - initialize(); - - // Wait for the xDS cluster resources to be retrieved and loaded. - // - // There are 5 total active clusters after the Envoy engine has finished initialization. - // - // 1. There is one strict dns cluster retrieved from Traffic Director: - // backend-svc-do-not-delete - // 2. There are two static clusters added by the EngineBuilder by default: - // base - // base_clear - ASSERT_TRUE(waitForGaugeGe("cluster_manager.active_clusters", 3)); - - // TODO(abeyad): Once we have a Envoy Mobile stats API, we can use it to check the - // actual cluster names. -} - -} // namespace -} // namespace Envoy - -int main(int argc, char** argv) { - Envoy::TestEnvironment::initializeOptions(argc, argv); - std::string error; - std::unique_ptr runfiles( - bazel::tools::cpp::runfiles::Runfiles::Create(argv[0], &error)); - RELEASE_ASSERT(runfiles != nullptr, error); - Envoy::TestEnvironment::setRunfiles(runfiles.get()); - - Envoy::Thread::MutexBasicLockable lock; - Envoy::Logger::Context logging_context(spdlog::level::level_enum::trace, - Envoy::Logger::Logger::DEFAULT_LOG_FORMAT, lock, false); - Envoy::Event::Libevent::Global::initialize(); - - testing::InitGoogleTest(); - return RUN_ALL_TESTS(); -} diff --git a/mobile/test/swift/EngineBuilderTests.swift b/mobile/test/swift/EngineBuilderTests.swift index 31c649b94eb8..c14110611544 100644 --- a/mobile/test/swift/EngineBuilderTests.swift +++ b/mobile/test/swift/EngineBuilderTests.swift @@ -3,8 +3,6 @@ import EnvoyEngine import Foundation import XCTest -// swiftlint:disable type_body_length - private struct TestFilter: Filter {} final class EngineBuilderTests: XCTestCase { @@ -314,89 +312,6 @@ final class EngineBuilderTests: XCTestCase { self.waitForExpectations(timeout: 0.01) } -#if ENVOY_MOBILE_XDS - func testAddingRtdsConfigurationWhenRunningEnvoy() { - let xdsBuilder = XdsBuilder(xdsServerAddress: "FAKE_SWIFT_ADDRESS", xdsServerPort: 0) - .addRuntimeDiscoveryService(resourceName: "some_rtds_resource", timeoutInSeconds: 14325) - let bootstrapDebugDescription = EngineBuilder() - .addEngineType(MockEnvoyEngine.self) - .setXds(xdsBuilder) - .bootstrapDebugDescription() - XCTAssertTrue(bootstrapDebugDescription.contains("some_rtds_resource")) - XCTAssertTrue(bootstrapDebugDescription.contains("initial_fetch_timeout { seconds: 14325 }")) - XCTAssertTrue(bootstrapDebugDescription.contains("FAKE_SWIFT_ADDRESS")) - } - - func testAddingCdsConfigurationWhenRunningEnvoy() { - let xdsBuilder = XdsBuilder(xdsServerAddress: "FAKE_SWIFT_ADDRESS", xdsServerPort: 0) - .addClusterDiscoveryService(cdsResourcesLocator: "FAKE_CDS_LOCATOR", timeoutInSeconds: 2543) - let bootstrapDebugDescription = EngineBuilder() - .addEngineType(MockEnvoyEngine.self) - .setXds(xdsBuilder) - .bootstrapDebugDescription() - XCTAssertTrue(bootstrapDebugDescription.contains("FAKE_CDS_LOCATOR")) - XCTAssertTrue(bootstrapDebugDescription.contains("initial_fetch_timeout { seconds: 2543 }")) - XCTAssertTrue(bootstrapDebugDescription.contains("FAKE_SWIFT_ADDRESS")) - } - - func testAddingDefaultCdsConfigurationWhenRunningEnvoy() { - let xdsBuilder = XdsBuilder(xdsServerAddress: "FAKE_SWIFT_ADDRESS", xdsServerPort: 0) - .addClusterDiscoveryService() - let bootstrapDebugDescription = EngineBuilder() - .addEngineType(MockEnvoyEngine.self) - .setXds(xdsBuilder) - .bootstrapDebugDescription() - XCTAssertTrue(bootstrapDebugDescription.contains("cds_config {")) - XCTAssertTrue(bootstrapDebugDescription.contains("initial_fetch_timeout { seconds: 5 }")) - } - - func testAddingXdsSecurityConfigurationWhenRunningEnvoy() { - let xdsBuilder = XdsBuilder(xdsServerAddress: "FAKE_SWIFT_ADDRESS", xdsServerPort: 0) - .addInitialStreamHeader(header: "x-goog-api-key", value: "A1B2C3") - .addInitialStreamHeader(header: "x-android-package", value: "com.google.myapp") - .setSslRootCerts(rootCerts: "fake_ssl_root_certs") - .addRuntimeDiscoveryService(resourceName: "some_rtds_resource", timeoutInSeconds: 14325) - let bootstrapDebugDescription = EngineBuilder() - .addEngineType(MockEnvoyEngine.self) - .setXds(xdsBuilder) - .bootstrapDebugDescription() - XCTAssertTrue(bootstrapDebugDescription.contains("x-goog-api-key")) - XCTAssertTrue(bootstrapDebugDescription.contains("A1B2C3")) - XCTAssertTrue(bootstrapDebugDescription.contains("x-android-package")) - XCTAssertTrue(bootstrapDebugDescription.contains("com.google.myapp")) - XCTAssertTrue(bootstrapDebugDescription.contains("fake_ssl_root_certs")) - } -#endif - - func testXDSDefaultValues() { - // rtds, ads, node_id, node_locality - let bootstrapDebugDescription = EngineBuilder() - .addEngineType(MockEnvoyEngine.self) - .bootstrapDebugDescription() - XCTAssertFalse(bootstrapDebugDescription.contains("rtds_layer {")) - XCTAssertFalse(bootstrapDebugDescription.contains("cds_config {")) - XCTAssertFalse(bootstrapDebugDescription.contains("ads_config {")) - XCTAssertTrue(bootstrapDebugDescription.contains(#"id: "envoy-mobile""#)) - XCTAssertFalse(bootstrapDebugDescription.contains("locality {")) - } - - func testCustomNodeID() { - let bootstrapDebugDescription = EngineBuilder() - .addEngineType(MockEnvoyEngine.self) - .setNodeID("SWIFT_TEST_NODE_ID") - .bootstrapDebugDescription() - XCTAssertTrue(bootstrapDebugDescription.contains(#"id: "SWIFT_TEST_NODE_ID""#)) - } - - func testCustomNodeLocality() { - let bootstrapDebugDescription = EngineBuilder() - .setNodeLocality(region: "SWIFT_REGION", zone: "SWIFT_ZONE", subZone: "SWIFT_SUB") - .bootstrapDebugDescription() - XCTAssertTrue(bootstrapDebugDescription.contains(#"region: "SWIFT_REGION""#)) - XCTAssertTrue(bootstrapDebugDescription.contains(#"zone: "SWIFT_ZONE""#)) - XCTAssertTrue(bootstrapDebugDescription.contains(#"sub_zone: "SWIFT_SUB""#)) - } - func testAddingKeyValueStoreToConfigurationWhenRunningEnvoy() { let expectation = self.expectation(description: "Run called with expected data") MockEnvoyEngine.onRunWithConfig = { config, _ in From 445349f37fa3fcb6814bdcb4981a2f00d60fb173 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 2 Jul 2024 14:58:16 -0400 Subject: [PATCH 29/31] tls: remove ocsp from client classeS (#35000) Risk Level: low Testing: updated tests Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- envoy/ssl/context.h | 40 +++++++++++++++ mobile/test/performance/files_em_does_not_use | 1 + source/common/tls/BUILD | 2 +- source/common/tls/client_context_impl.h | 1 - source/common/tls/context_impl.h | 3 +- source/common/tls/ocsp/ocsp.cc | 23 +++++---- source/common/tls/ocsp/ocsp.h | 51 ++++--------------- source/common/tls/server_context_impl.cc | 2 +- test/common/tls/ocsp/ocsp_test.cc | 14 ++--- 9 files changed, 74 insertions(+), 63 deletions(-) diff --git a/envoy/ssl/context.h b/envoy/ssl/context.h index 738a7b790f6a..7cb4e36f175f 100644 --- a/envoy/ssl/context.h +++ b/envoy/ssl/context.h @@ -5,6 +5,7 @@ #include "envoy/admin/v3/certs.pb.h" #include "envoy/common/pure.h" +#include "envoy/common/time.h" #include "absl/types/optional.h" @@ -50,5 +51,44 @@ using ClientContextSharedPtr = std::shared_ptr; class ServerContext : public virtual Context {}; using ServerContextSharedPtr = std::shared_ptr; +class OcspResponseWrapper { +public: + virtual ~OcspResponseWrapper() = default; + /** + * @returns the seconds until this OCSP response expires. + */ + virtual uint64_t secondsUntilExpiration() const PURE; + + /** + * @return The beginning of the validity window for this response. + */ + virtual Envoy::SystemTime getThisUpdate() const PURE; + + /** + * The time at which this response is considered to expire. If + * the underlying response does not have a value, then the current + * time is returned. + * + * @return The end of the validity window for this response. + */ + virtual Envoy::SystemTime getNextUpdate() const PURE; + + /** + * Determines whether the OCSP response can no longer be considered valid. + * This can be true if the nextUpdate field of the response has passed + * or is not present, indicating that there is always more updated information + * available. + * + * @returns bool if the OCSP response is expired. + */ + virtual bool isExpired() PURE; + + /** + * @return std::vector& a reference to the underlying bytestring representation + * of the OCSP response + */ + virtual const std::vector& rawBytes() const PURE; +}; + } // namespace Ssl } // namespace Envoy diff --git a/mobile/test/performance/files_em_does_not_use b/mobile/test/performance/files_em_does_not_use index 5dc0d801e075..e6c1bf219a01 100644 --- a/mobile/test/performance/files_em_does_not_use +++ b/mobile/test/performance/files_em_does_not_use @@ -15,3 +15,4 @@ source/common/tls/server_ssl_socket.h source/common/router/rds_impl.h source/common/router/vhds.h source/common/tls/server_context_impl.h +source/common/tls/ocsp/ocsp.h diff --git a/source/common/tls/BUILD b/source/common/tls/BUILD index 118995f5037e..949467817bd1 100644 --- a/source/common/tls/BUILD +++ b/source/common/tls/BUILD @@ -203,7 +203,6 @@ envoy_cc_library( "//source/common/stats:symbol_table_lib", "//source/common/stats:utility_lib", "//source/common/tls/cert_validator:cert_validator_lib", - "//source/common/tls/ocsp:ocsp_lib", "//source/common/tls/private_key:private_key_manager_lib", "@com_github_google_quiche//:quic_core_crypto_proof_source_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", @@ -221,6 +220,7 @@ envoy_cc_library( ], deps = [ ":context_lib", + "//source/common/tls/ocsp:ocsp_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto", ], diff --git a/source/common/tls/client_context_impl.h b/source/common/tls/client_context_impl.h index dbe914fdede7..2c01ebaa83c9 100644 --- a/source/common/tls/client_context_impl.h +++ b/source/common/tls/client_context_impl.h @@ -22,7 +22,6 @@ #include "source/common/tls/cert_validator/cert_validator.h" #include "source/common/tls/context_impl.h" #include "source/common/tls/context_manager_impl.h" -#include "source/common/tls/ocsp/ocsp.h" #include "source/common/tls/stats.h" #include "absl/synchronization/mutex.h" diff --git a/source/common/tls/context_impl.h b/source/common/tls/context_impl.h index 9b508f862a6a..09875aa312de 100644 --- a/source/common/tls/context_impl.h +++ b/source/common/tls/context_impl.h @@ -21,7 +21,6 @@ #include "source/common/stats/symbol_table.h" #include "source/common/tls/cert_validator/cert_validator.h" #include "source/common/tls/context_manager_impl.h" -#include "source/common/tls/ocsp/ocsp.h" #include "source/common/tls/stats.h" #include "absl/synchronization/mutex.h" @@ -47,7 +46,7 @@ struct TlsContext { bssl::UniquePtr ssl_ctx_; bssl::UniquePtr cert_chain_; std::string cert_chain_file_path_; - Extensions::TransportSockets::Tls::Ocsp::OcspResponseWrapperPtr ocsp_response_; + std::unique_ptr ocsp_response_; bool is_ecdsa_{}; bool is_must_staple_{}; Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; diff --git a/source/common/tls/ocsp/ocsp.cc b/source/common/tls/ocsp/ocsp.cc index bee9453a5bd2..b6b52eda7649 100644 --- a/source/common/tls/ocsp/ocsp.cc +++ b/source/common/tls/ocsp/ocsp.cc @@ -109,17 +109,18 @@ absl::Status validateResponse(std::unique_ptr& response) { return absl::OkStatus(); } -absl::StatusOr> -OcspResponseWrapper::create(std::vector der_response, TimeSource& time_source) { +absl::StatusOr> +OcspResponseWrapperImpl::create(std::vector der_response, TimeSource& time_source) { auto response_or_error = readDerEncodedOcspResponse(der_response); RETURN_IF_NOT_OK(response_or_error.status()); RETURN_IF_NOT_OK(validateResponse(response_or_error.value())); - return std::unique_ptr{ - new OcspResponseWrapper(der_response, time_source, std::move(response_or_error.value()))}; + return std::unique_ptr{ + new OcspResponseWrapperImpl(der_response, time_source, std::move(response_or_error.value()))}; } -OcspResponseWrapper::OcspResponseWrapper(std::vector der_response, TimeSource& time_source, - std::unique_ptr&& response) +OcspResponseWrapperImpl::OcspResponseWrapperImpl(std::vector der_response, + TimeSource& time_source, + std::unique_ptr&& response) : raw_bytes_(std::move(der_response)), response_(std::move(response)), time_source_(time_source) { auto& this_update = response_->response_->getThisUpdate(); @@ -135,18 +136,18 @@ OcspResponseWrapper::OcspResponseWrapper(std::vector der_response, Time // Though different issuers could produce certificates with the same serial // number, this is check is to prevent operator error and a collision in this // case is unlikely. -bool OcspResponseWrapper::matchesCertificate(X509& cert) const { +bool OcspResponseWrapperImpl::matchesCertificate(X509& cert) const { std::string cert_serial_number = CertUtility::getSerialNumberFromCertificate(cert); std::string resp_cert_serial_number = response_->response_->getCertSerialNumber(); return resp_cert_serial_number == cert_serial_number; } -bool OcspResponseWrapper::isExpired() { +bool OcspResponseWrapperImpl::isExpired() { auto& next_update = response_->response_->getNextUpdate(); return next_update == absl::nullopt || next_update < time_source_.systemTime(); } -uint64_t OcspResponseWrapper::secondsUntilExpiration() const { +uint64_t OcspResponseWrapperImpl::secondsUntilExpiration() const { auto& next_update = response_->response_->getNextUpdate(); auto now = time_source_.systemTime(); if (!next_update || next_update.value() <= now) { @@ -155,11 +156,11 @@ uint64_t OcspResponseWrapper::secondsUntilExpiration() const { return std::chrono::duration_cast(next_update.value() - now).count(); } -Envoy::SystemTime OcspResponseWrapper::getThisUpdate() const { +Envoy::SystemTime OcspResponseWrapperImpl::getThisUpdate() const { return response_->response_->getThisUpdate(); } -Envoy::SystemTime OcspResponseWrapper::getNextUpdate() const { +Envoy::SystemTime OcspResponseWrapperImpl::getNextUpdate() const { auto& next_update = response_->response_->getNextUpdate(); if (next_update) { return *next_update; diff --git a/source/common/tls/ocsp/ocsp.h b/source/common/tls/ocsp/ocsp.h index b3c25490a30c..7a86e742264d 100644 --- a/source/common/tls/ocsp/ocsp.h +++ b/source/common/tls/ocsp/ocsp.h @@ -6,6 +6,7 @@ #include "envoy/common/exception.h" #include "envoy/common/time.h" +#include "envoy/ssl/context.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" @@ -179,19 +180,13 @@ struct OcspResponse { /** * A wrapper used to own and query an OCSP response in DER-encoded format. */ -class OcspResponseWrapper { +class OcspResponseWrapperImpl : public Ssl::OcspResponseWrapper { public: - OcspResponseWrapper(std::vector der_response, TimeSource& time_source, - std::unique_ptr&& response); - static absl::StatusOr> + OcspResponseWrapperImpl(std::vector der_response, TimeSource& time_source, + std::unique_ptr&& response); + static absl::StatusOr> create(std::vector der_response, TimeSource& time_source); - /** - * @return std::vector& a reference to the underlying bytestring representation - * of the OCSP response - */ - const std::vector& rawBytes() const { return raw_bytes_; } - /** * @return OcspResponseStatus whether the OCSP response was successfully created * or a status indicating an error in the OCSP process @@ -204,34 +199,12 @@ class OcspResponseWrapper { */ bool matchesCertificate(X509& cert) const; - /** - * Determines whether the OCSP response can no longer be considered valid. - * This can be true if the nextUpdate field of the response has passed - * or is not present, indicating that there is always more updated information - * available. - * - * @returns bool if the OCSP response is expired. - */ - bool isExpired(); - - /** - * @returns the seconds until this OCSP response expires. - */ - uint64_t secondsUntilExpiration() const; - - /** - * @return The beginning of the validity window for this response. - */ - Envoy::SystemTime getThisUpdate() const; - - /** - * The time at which this response is considered to expire. If - * the underlying response does not have a value, then the current - * time is returned. - * - * @return The end of the validity window for this response. - */ - Envoy::SystemTime getNextUpdate() const; + // OcspResponseWrapper + uint64_t secondsUntilExpiration() const override; + Envoy::SystemTime getThisUpdate() const override; + Envoy::SystemTime getNextUpdate() const override; + bool isExpired() override; + const std::vector& rawBytes() const override { return raw_bytes_; } private: const std::vector raw_bytes_; @@ -239,8 +212,6 @@ class OcspResponseWrapper { TimeSource& time_source_; }; -using OcspResponseWrapperPtr = std::unique_ptr; - /** * `ASN.1` DER-encoded parsing functions similar to `Asn1Utility` but specifically * for structures related to OCSP. diff --git a/source/common/tls/server_context_impl.cc b/source/common/tls/server_context_impl.cc index 37e6df7a0ae7..5cb1facf7d4d 100644 --- a/source/common/tls/server_context_impl.cc +++ b/source/common/tls/server_context_impl.cc @@ -195,7 +195,7 @@ ServerContextImpl::ServerContextImpl(Stats::Scope& scope, } } else { auto response_or_error = - Ocsp::OcspResponseWrapper::create(ocsp_resp_bytes, factory_context_.timeSource()); + Ocsp::OcspResponseWrapperImpl::create(ocsp_resp_bytes, factory_context_.timeSource()); THROW_IF_STATUS_NOT_OK(response_or_error, throw); if (!response_or_error.value()->matchesCertificate(*ctx.cert_chain_)) { throw EnvoyException("OCSP response does not match its TLS certificate"); diff --git a/test/common/tls/ocsp/ocsp_test.cc b/test/common/tls/ocsp/ocsp_test.cc index 10cef256fb9b..793ea5f1e514 100644 --- a/test/common/tls/ocsp/ocsp_test.cc +++ b/test/common/tls/ocsp/ocsp_test.cc @@ -36,7 +36,7 @@ class OcspFullResponseParsingTest : public testing::Test { void setup(std::string response_filename) { auto der_response = readFile(response_filename); - response_ = OcspResponseWrapper::create(der_response, time_system_).value(); + response_ = OcspResponseWrapperImpl::create(der_response, time_system_).value(); EXPECT_EQ(response_->rawBytes(), der_response); } @@ -51,7 +51,7 @@ class OcspFullResponseParsingTest : public testing::Test { protected: Event::SimulatedTimeSystem time_system_; - OcspResponseWrapperPtr response_; + std::unique_ptr response_; }; TEST_F(OcspFullResponseParsingTest, GoodCertTest) { @@ -107,7 +107,7 @@ TEST_F(OcspFullResponseParsingTest, ResponderIdKeyHashTest) { TEST_F(OcspFullResponseParsingTest, MultiCertResponseTest) { auto resp_bytes = readFile("multiple_cert_ocsp_resp.der"); - EXPECT_EQ(OcspResponseWrapper::create(resp_bytes, time_system_).status().message(), + EXPECT_EQ(OcspResponseWrapperImpl::create(resp_bytes, time_system_).status().message(), "OCSP Response must be for one certificate only"); } @@ -119,7 +119,7 @@ TEST_F(OcspFullResponseParsingTest, UnsuccessfulResponseTest) { 0xau, 1, 2, // no response bytes }; - EXPECT_EQ(OcspResponseWrapper::create(data, time_system_).status().message(), + EXPECT_EQ(OcspResponseWrapperImpl::create(data, time_system_).status().message(), "OCSP response was unsuccessful"); } @@ -131,7 +131,7 @@ TEST_F(OcspFullResponseParsingTest, NoResponseBodyTest) { 0xau, 1, 0, // no response bytes }; - EXPECT_EQ(OcspResponseWrapper::create(data, time_system_).status().message(), + EXPECT_EQ(OcspResponseWrapperImpl::create(data, time_system_).status().message(), "OCSP response has no body"); } @@ -140,7 +140,7 @@ TEST_F(OcspFullResponseParsingTest, OnlyOneResponseInByteStringTest) { auto resp2_bytes = readFile("revoked_ocsp_resp.der"); resp_bytes.insert(resp_bytes.end(), resp2_bytes.begin(), resp2_bytes.end()); - EXPECT_EQ(OcspResponseWrapper::create(resp_bytes, time_system_).status().message(), + EXPECT_EQ(OcspResponseWrapperImpl::create(resp_bytes, time_system_).status().message(), "Data contained more than a single OCSP response"); } @@ -148,7 +148,7 @@ TEST_F(OcspFullResponseParsingTest, ParseOcspResponseWrongTagTest) { auto resp_bytes = readFile("good_ocsp_resp.der"); // Change the SEQUENCE tag to an `OCTETSTRING` tag resp_bytes[0] = 0x4u; - EXPECT_EQ(OcspResponseWrapper::create(resp_bytes, time_system_).status().message(), + EXPECT_EQ(OcspResponseWrapperImpl::create(resp_bytes, time_system_).status().message(), "OCSP Response is not a well-formed ASN.1 SEQUENCE"); } From e4e9a01832b444f6eeea13ea10425f77690e67c1 Mon Sep 17 00:00:00 2001 From: Bin Wu <46450037+wu-bin@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:50:44 -0400 Subject: [PATCH 30/31] Add EnvoyQuicConnectionContextListener to EnvoyQuicServerSession. (#34975) Commit Message: Add EnvoyQuicConnectionContextListener to EnvoyQuicServerSession. Additional Description: This will start tracking the work done in the scope of EnvoyQuicServerSession's QuicAlarm callbacks. Risk Level: low Testing: No crash in existing QUIC related tests. Docs Changes: - Release Notes: - Platform Specific Features: - Signed-off-by: Bin Wu --- source/common/quic/BUILD | 3 +++ .../common/quic/envoy_quic_server_session.cc | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index 6e2d8125be4b..386529600880 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -282,6 +282,9 @@ envoy_cc_library( "envoy_quic_server_session.h", "envoy_quic_server_stream.h", ], + external_deps = [ + "abseil_optional", + ], tags = ["nofips"], deps = [ ":envoy_quic_connection_debug_visitor_factory_interface", diff --git a/source/common/quic/envoy_quic_server_session.cc b/source/common/quic/envoy_quic_server_session.cc index 8d7438d55d85..fdeeb8edb4c8 100644 --- a/source/common/quic/envoy_quic_server_session.cc +++ b/source/common/quic/envoy_quic_server_session.cc @@ -5,14 +5,33 @@ #include #include "source/common/common/assert.h" +#include "source/common/common/scope_tracker.h" #include "source/common/quic/envoy_quic_connection_debug_visitor_factory_interface.h" #include "source/common/quic/envoy_quic_proof_source.h" #include "source/common/quic/envoy_quic_server_stream.h" #include "source/common/quic/quic_filter_manager_connection_impl.h" +#include "absl/types/optional.h" + namespace Envoy { namespace Quic { +namespace { +class EnvoyQuicConnectionContextListener : public quic::QuicConnectionContextListener { +public: + EnvoyQuicConnectionContextListener(const ScopeTrackedObject* object, Event::ScopeTracker& tracker) + : object_(object), tracker_(tracker) {} + +private: + void Activate() override { state_.emplace(object_, tracker_); } + void Deactivate() override { state_.reset(); } + + const ScopeTrackedObject* object_; + Event::ScopeTracker& tracker_; + absl::optional state_; +}; +} // namespace + EnvoyQuicServerSession::EnvoyQuicServerSession( const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, std::unique_ptr connection, quic::QuicSession::Visitor* visitor, @@ -40,6 +59,8 @@ EnvoyQuicServerSession::EnvoyQuicServerSession( debug_visitor_ = debug_visitor_factory->createQuicConnectionDebugVisitor(this, streamInfo()); quic_connection_->set_debug_visitor(debug_visitor_.get()); } + quic_connection_->set_context_listener( + std::make_unique(this, dispatcher)); } EnvoyQuicServerSession::~EnvoyQuicServerSession() { From 85f8a25ce7be707279703eb767ae5652dd3625c0 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Tue, 2 Jul 2024 16:46:32 -0400 Subject: [PATCH 31/31] docs: fix LRS example link (#35002) Commit Message: docs: fix LRS example link Additional Description: Fixing a broken link. Risk Level: low - docs only Testing: N/A Docs Changes: Updated Release Notes: N/A Platform Specific Features: N/A --------- Signed-off-by: Adi Suissa-Peleg --- .../arch_overview/upstream/load_reporting_service.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/root/intro/arch_overview/upstream/load_reporting_service.rst b/docs/root/intro/arch_overview/upstream/load_reporting_service.rst index 669bce160592..e81fb119893f 100644 --- a/docs/root/intro/arch_overview/upstream/load_reporting_service.rst +++ b/docs/root/intro/arch_overview/upstream/load_reporting_service.rst @@ -10,6 +10,12 @@ This will initiate a bi-directional stream with a management server. Upon connec server can send a :ref:`LoadStatsResponse ` to a node it is interested in getting the load reports for. Envoy in this node will start sending :ref:`LoadStatsRequest `. This is done periodically -based on the :ref:`load reporting interval ` +based on the :ref:`load reporting interval `. -Envoy config with LRS can be found at :repo:`/examples/load-reporting-service/service-envoy-w-lrs.yaml`. +Example of an Envoy config with LRS: + +.. literalinclude:: /start/sandboxes/_include/load-reporting-service/envoy.yaml + :language: yaml + :linenos: + :emphasize-lines: 60-64 + :caption: :download:`envoy.yaml `