From aa8d0f7d7a2866b5f81dfe46e975b80de26a0669 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 6 Aug 2024 10:15:47 -0400 Subject: [PATCH] envoy: allowing alt-svc from IPs, fixing HTTP/3 cert bypass (#35551) This adds a real HTTP/3 test for Envoy mobile's Cronet APIs. In order for this to work it fixes 2 issues in Envoy, the first that the connection grid didn't do lookups for ip based hostname. The second that ACCEPT_UNTRUSTED didn't fully work for QUIC. Risk Level: low Testing: e2e test Docs Changes: n/a Release Notes: inline [Optional Runtime guard:] guarded both functional changes --------- Signed-off-by: Alyssa Wilk --- changelogs/current.yaml | 8 + mobile/test/common/integration/test_server.cc | 11 +- mobile/test/common/integration/test_server.h | 2 +- .../integration/test_server_interface.cc | 2 +- .../engine/testing/HttpTestServerFactory.java | 6 +- .../engine/testing/QuicTestServerTest.java | 2 +- mobile/test/java/org/chromium/net/BUILD | 27 ++++ .../org/chromium/net/CronetHttp3Test.java | 139 ++++++++++++++++++ .../jni/jni_http_proxy_test_server_factory.cc | 2 +- .../test/jni/jni_http_test_server_factory.cc | 5 +- .../kotlin/integration/ReceiveDataTest.kt | 1 + .../kotlin/integration/ReceiveTrailersTest.kt | 1 + .../test/kotlin/integration/SendDataTest.kt | 1 + source/common/http/conn_pool_grid.cc | 15 +- .../common/quic/envoy_quic_proof_verifier.cc | 11 +- .../common/quic/envoy_quic_proof_verifier.h | 8 +- .../quic_client_transport_socket_factory.cc | 7 +- source/common/runtime/runtime_features.cc | 2 + test/common/http/conn_pool_grid_test.cc | 38 ++++- 19 files changed, 260 insertions(+), 28 deletions(-) create mode 100644 mobile/test/java/org/chromium/net/CronetHttp3Test.java diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f0ff083bdd3d..e59477c5e4e9 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -21,6 +21,14 @@ minor_behavior_changes: change: | Added support for :ref:`connection_pool_per_downstream_connection ` flag in tcp connection pool. +- area: http3 + change: | + The ACCEPT_UNTRUSTED option now works more consistently for HTTP/3 requests. This change is + guarded by ``envoy.reloadable_features.extend_h3_accept_untrusted``. +- area: http3 + change: | + HTTP/3 alt-svc headers will now be respected from IP-address-based hostnames. This change is + guarded by runtime guard ``envoy.reloadable_features.allow_alt_svc_for_ips``. - area: lua change: | When Lua script executes httpCall, backpressure is exercised when receiving body from downstream client. This behavior can be reverted diff --git a/mobile/test/common/integration/test_server.cc b/mobile/test/common/integration/test_server.cc index c8727516a5eb..3b3ad20b88c3 100644 --- a/mobile/test/common/integration/test_server.cc +++ b/mobile/test/common/integration/test_server.cc @@ -29,7 +29,7 @@ namespace Envoy { namespace { -std::unique_ptr baseProxyConfig(bool http) { +std::unique_ptr baseProxyConfig(bool http, int port) { std::unique_ptr bootstrap = std::make_unique(); @@ -40,7 +40,7 @@ std::unique_ptr baseProxyConfig(bool ht auto* base_address = listener->mutable_address(); base_address->mutable_socket_address()->set_protocol(envoy::config::core::v3::SocketAddress::TCP); base_address->mutable_socket_address()->set_address("127.0.0.1"); - base_address->mutable_socket_address()->set_port_value(0); + base_address->mutable_socket_address()->set_port_value(port); envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager hcm; hcm.set_stat_prefix("remote hcm"); @@ -137,7 +137,8 @@ TestServer::TestServer() Envoy::ExtensionRegistry::registerFactories(); } -void TestServer::start(TestServerType type) { +void TestServer::start(TestServerType type, int port) { + port_ = port; ASSERT(!upstream_); // pre-setup: see https://github.com/envoyproxy/envoy/blob/main/test/test_runner.cc Logger::Context logging_state(spdlog::level::level_enum::err, @@ -180,7 +181,7 @@ void TestServer::start(TestServerType type) { test_server_ = IntegrationTestServer::create( "", Network::Address::IpVersion::v4, nullptr, nullptr, {}, time_system_, *api_, false, absl::nullopt, Server::FieldValidationConfig(), 1, std::chrono::seconds(1), - Server::DrainStrategy::Gradual, nullptr, false, false, baseProxyConfig(true)); + Server::DrainStrategy::Gradual, nullptr, false, false, baseProxyConfig(true, port_)); test_server_->waitUntilListenersReady(); ENVOY_LOG_MISC(debug, "Http proxy is now running"); return; @@ -195,7 +196,7 @@ void TestServer::start(TestServerType type) { test_server_ = IntegrationTestServer::create( "", Network::Address::IpVersion::v4, nullptr, nullptr, {}, time_system_, *api_, false, absl::nullopt, Server::FieldValidationConfig(), 1, std::chrono::seconds(1), - Server::DrainStrategy::Gradual, nullptr, false, false, baseProxyConfig(false)); + Server::DrainStrategy::Gradual, nullptr, false, false, baseProxyConfig(false, port_)); test_server_->waitUntilListenersReady(); ENVOY_LOG_MISC(debug, "Https proxy is now running"); return; diff --git a/mobile/test/common/integration/test_server.h b/mobile/test/common/integration/test_server.h index 68f6bbd59484..c4156aa667b8 100644 --- a/mobile/test/common/integration/test_server.h +++ b/mobile/test/common/integration/test_server.h @@ -31,7 +31,7 @@ class TestServer : public ListenerHooks { /** * Starts the test server. This function blocks until the test server is ready to accept requests. */ - void start(TestServerType type); + void start(TestServerType type, int port = 0); /** * Shutdowns the server server. This function blocks until all the resources have been freed. diff --git a/mobile/test/common/integration/test_server_interface.cc b/mobile/test/common/integration/test_server_interface.cc index e30ba593babd..91dc5f46e74f 100644 --- a/mobile/test/common/integration/test_server_interface.cc +++ b/mobile/test/common/integration/test_server_interface.cc @@ -12,7 +12,7 @@ void start_server(Envoy::TestServerType test_server_type) { weak_test_server_ = strong_test_server_; if (auto server = test_server()) { - server->start(test_server_type); + server->start(test_server_type, 0); } } diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/HttpTestServerFactory.java b/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/HttpTestServerFactory.java index 7e1599f06203..ee4e7f84d5a5 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/HttpTestServerFactory.java +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/HttpTestServerFactory.java @@ -53,8 +53,8 @@ private HttpTestServer(long handle, String ipAddress, int port, String address) * @param trailers the response headers * @return the `HttpTestServer` instance */ - public static native HttpTestServer start(int type, Map headers, String body, - Map trailers); + public static native HttpTestServer start(int type, int port, Map headers, + String body, Map trailers); /** * A convenience method to start the server with an empty response headers, body, and trailers. @@ -64,6 +64,6 @@ public static native HttpTestServer start(int type, Map headers, * @return the `HttpTestServer` instance */ public static HttpTestServer start(int type) { - return start(type, Collections.emptyMap(), "", Collections.emptyMap()); + return start(type, 0, Collections.emptyMap(), "", Collections.emptyMap()); } } diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServerTest.java b/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServerTest.java index 654fd86e3d94..67d92c274089 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServerTest.java +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServerTest.java @@ -41,7 +41,7 @@ public void setUpEngine() throws Exception { Map headers = new HashMap<>(); headers.put("Cache-Control", "max-age=0"); headers.put("Content-Type", "text/plain"); - httpTestServer = HttpTestServerFactory.start(HttpTestServerFactory.Type.HTTP3, headers, + httpTestServer = HttpTestServerFactory.start(HttpTestServerFactory.Type.HTTP3, 0, headers, "This is a simple text file served by QUIC.\n", Collections.emptyMap()); diff --git a/mobile/test/java/org/chromium/net/BUILD b/mobile/test/java/org/chromium/net/BUILD index 19773583241e..aefe1cb0152d 100644 --- a/mobile/test/java/org/chromium/net/BUILD +++ b/mobile/test/java/org/chromium/net/BUILD @@ -221,6 +221,33 @@ envoy_mobile_android_test( ], ) +envoy_mobile_android_test( + name = "cronet_http3_test", + srcs = [ + "CronetHttp3Test.java", + ], + native_deps = [ + "//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", + test_class = "org.chromium.net.CronetHttp3Test", + deps = [ + "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", + "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", + "//library/java/org/chromium/net", + "//library/java/org/chromium/net/impl:cronvoy", + "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", + "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", + "//test/java/io/envoyproxy/envoymobile/engine/testing:http_test_server_factory_lib", + "//test/java/org/chromium/net/testing", + ], +) + envoy_mobile_android_test( name = "cronet_url_request_context_test", srcs = [ diff --git a/mobile/test/java/org/chromium/net/CronetHttp3Test.java b/mobile/test/java/org/chromium/net/CronetHttp3Test.java new file mode 100644 index 000000000000..9b0800b18e67 --- /dev/null +++ b/mobile/test/java/org/chromium/net/CronetHttp3Test.java @@ -0,0 +1,139 @@ +package org.chromium.net; + +import static org.chromium.net.testing.CronetTestRule.getContext; +import static org.junit.Assert.assertEquals; + +import org.chromium.net.impl.CronvoyUrlRequestContext; +import io.envoyproxy.envoymobile.engine.EnvoyEngine; +import org.chromium.net.impl.CronvoyLogger; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import org.chromium.net.impl.CronvoyUrlRequestContext; +import org.chromium.net.impl.NativeCronvoyEngineBuilderImpl; +import org.chromium.net.testing.CronetTestRule; +import org.chromium.net.testing.CronetTestRule.CronetTestFramework; +import org.chromium.net.testing.CronetTestRule.RequiresMinApi; +import org.chromium.net.testing.Feature; +import org.chromium.net.testing.TestUrlRequestCallback; +import org.chromium.net.testing.TestUrlRequestCallback.ResponseStep; +import io.envoyproxy.envoymobile.engine.JniLibrary; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import io.envoyproxy.envoymobile.engine.testing.HttpTestServerFactory; +import java.util.HashMap; +import java.util.Map; +import java.util.Collections; + +/** + * Test CronetEngine with production HTTP/3 logic + */ +@RunWith(RobolectricTestRunner.class) +public class CronetHttp3Test { + @Rule public final CronetTestRule mTestRule = new CronetTestRule(); + + private static final String TAG = CronetHttp3Test.class.getSimpleName(); + + // URLs used for tests. + + // If true, dump envoy logs on test completion. + // Ideally we could override this from the command line but that's TBD. + private boolean printEnvoyLogs = false; + // The HTTP/2 server, set up to alt-svc to the HTTP/3 server + private HttpTestServerFactory.HttpTestServer http2TestServer; + // The HTTP/3 server + private HttpTestServerFactory.HttpTestServer http3TestServer; + // An optional CronvoyLogger, set up if printEnvoyLogs is true. + private CronvoyLogger logger; + // The engine for this test. + private CronvoyUrlRequestContext cronvoyEngine; + + @BeforeClass + public static void loadJniLibrary() { + JniLibrary.loadTestLibrary(); + } + + public void setUp(boolean setUpLogging) throws Exception { + // Set up the HTTP/3 server + Map headers = new HashMap<>(); + http3TestServer = HttpTestServerFactory.start(HttpTestServerFactory.Type.HTTP3, 0, headers, + "This is a simple text file served by QUIC.\n", + Collections.emptyMap()); + // Next set up the HTTP/2 server, advertising HTTP/3 support for the HTTP/3 server + String altSvc = "h3=\":" + http3TestServer.getPort() + "\"; ma=86400"; + headers.put("alt-svc", altSvc); + // Note that the HTTP/2 server must start on the same port as Envoy currently does not accept + // alt-svc with differing ports. This may cause problems if this UDP port is in use at which + // point listening on 127.0.0.N where N!=1 may improve flakiness. + http2TestServer = HttpTestServerFactory.start( + HttpTestServerFactory.Type.HTTP2_WITH_TLS, http3TestServer.getPort(), headers, + "This is a simple text file served by QUIC.\n", Collections.emptyMap()); + + // Optionally, set up logging. This will slow down the tests a bit but make debugging much + // easier. + if (setUpLogging) { + logger = new CronvoyLogger() { + @Override + public void log(int logLevel, String message) { + System.out.print(message); + } + }; + } + } + + @After + public void tearDown() throws Exception { + // Shut down Envoy and the test servers. + cronvoyEngine.shutdown(); + http2TestServer.shutdown(); + http3TestServer.shutdown(); + } + + @Test + @SmallTest + @Feature({"Cronet"}) + public void testInitEngineAndStartRequest() throws Exception { + // Ideally we could override this from the command line but that's TBD. + setUp(printEnvoyLogs); + + // Set up the Envoy engine. + NativeCronvoyEngineBuilderImpl nativeCronetEngineBuilder = + new NativeCronvoyEngineBuilderImpl(ApplicationProvider.getApplicationContext()); + if (printEnvoyLogs) { + nativeCronetEngineBuilder.setLogger(logger); + nativeCronetEngineBuilder.setLogLevel(EnvoyEngine.LogLevel.TRACE); + } + // Make sure the handshake will work despite lack of real certs. + nativeCronetEngineBuilder.setMockCertVerifierForTesting(); + cronvoyEngine = new CronvoyUrlRequestContext(nativeCronetEngineBuilder); + + // Do a request to https://127.0.0.1:test_server_port/ + TestUrlRequestCallback callback1 = new TestUrlRequestCallback(); + String newUrl = "https://" + http2TestServer.getAddress() + "/"; + UrlRequest.Builder urlRequestBuilder = + cronvoyEngine.newUrlRequestBuilder(newUrl, callback1, callback1.getExecutor()); + urlRequestBuilder.build().start(); + callback1.blockForDone(); + + // Make sure the request succeeded. It should go out over HTTP/2 as it's the first + // request and HTTP/3 support is not established. + assertEquals(200, callback1.mResponseInfo.getHttpStatusCode()); + assertEquals("h2", callback1.mResponseInfo.getNegotiatedProtocol()); + + // Set up a second request, which will hopefully go out over HTTP/3 due to alt-svc + // advertisement. + TestUrlRequestCallback callback2 = new TestUrlRequestCallback(); + UrlRequest.Builder urlRequestBuilder2 = + cronvoyEngine.newUrlRequestBuilder(newUrl, callback2, callback2.getExecutor()); + urlRequestBuilder2.build().start(); + callback2.blockForDone(); + + // Verify the second request used HTTP/3 + assertEquals(200, callback2.mResponseInfo.getHttpStatusCode()); + assertEquals("h3", callback2.mResponseInfo.getNegotiatedProtocol()); + } +} diff --git a/mobile/test/jni/jni_http_proxy_test_server_factory.cc b/mobile/test/jni/jni_http_proxy_test_server_factory.cc index 2314d0868397..877dfa81dcfb 100644 --- a/mobile/test/jni/jni_http_proxy_test_server_factory.cc +++ b/mobile/test/jni/jni_http_proxy_test_server_factory.cc @@ -22,7 +22,7 @@ Java_io_envoyproxy_envoymobile_engine_testing_HttpProxyTestServerFactory_start(J Envoy::ExtensionRegistry::registerFactories(); Envoy::TestServer* test_server = new Envoy::TestServer(); - test_server->start(static_cast(type)); + test_server->start(static_cast(type), 0); jclass java_http_proxy_server_factory_class = jni_helper.findClass( "io/envoyproxy/envoymobile/engine/testing/HttpProxyTestServerFactory$HttpProxyTestServer"); diff --git a/mobile/test/jni/jni_http_test_server_factory.cc b/mobile/test/jni/jni_http_test_server_factory.cc index dd6409b038ab..eb68d597007d 100644 --- a/mobile/test/jni/jni_http_test_server_factory.cc +++ b/mobile/test/jni/jni_http_test_server_factory.cc @@ -18,12 +18,13 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { extern "C" JNIEXPORT jobject JNICALL Java_io_envoyproxy_envoymobile_engine_testing_HttpTestServerFactory_start( - JNIEnv* env, jclass, jint type, jobject headers, jstring body, jobject trailers) { + JNIEnv* env, jclass, jint type, jint requested_port, jobject headers, jstring body, + jobject trailers) { Envoy::JNI::JniHelper jni_helper(env); Envoy::ExtensionRegistry::registerFactories(); Envoy::TestServer* test_server = new Envoy::TestServer(); - test_server->start(static_cast(type)); + test_server->start(static_cast(type), requested_port); auto cpp_headers = Envoy::JNI::javaMapToCppMap(jni_helper, headers); auto cpp_body = Envoy::JNI::javaStringToCppString(jni_helper, body); diff --git a/mobile/test/kotlin/integration/ReceiveDataTest.kt b/mobile/test/kotlin/integration/ReceiveDataTest.kt index bab47e1cc2f6..630c55d1c766 100644 --- a/mobile/test/kotlin/integration/ReceiveDataTest.kt +++ b/mobile/test/kotlin/integration/ReceiveDataTest.kt @@ -31,6 +31,7 @@ class ReceiveDataTest { httpTestServer = HttpTestServerFactory.start( HttpTestServerFactory.Type.HTTP2_WITH_TLS, + 0, mapOf(), "data", mapOf() diff --git a/mobile/test/kotlin/integration/ReceiveTrailersTest.kt b/mobile/test/kotlin/integration/ReceiveTrailersTest.kt index abb176bc905d..6d43cad5c61a 100644 --- a/mobile/test/kotlin/integration/ReceiveTrailersTest.kt +++ b/mobile/test/kotlin/integration/ReceiveTrailersTest.kt @@ -35,6 +35,7 @@ class ReceiveTrailersTest { httpTestServer = HttpTestServerFactory.start( HttpTestServerFactory.Type.HTTP2_WITH_TLS, + 0, mapOf(), "data", mapOf(TRAILER_NAME to TRAILER_VALUE) diff --git a/mobile/test/kotlin/integration/SendDataTest.kt b/mobile/test/kotlin/integration/SendDataTest.kt index 094db7c32a26..05d5c1533bfe 100644 --- a/mobile/test/kotlin/integration/SendDataTest.kt +++ b/mobile/test/kotlin/integration/SendDataTest.kt @@ -36,6 +36,7 @@ class SendDataTest { httpTestServer = HttpTestServerFactory.start( HttpTestServerFactory.Type.HTTP2_WITH_TLS, + 0, mapOf(), "data", mapOf() diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index 8605f2dbc9c2..21dfc6a0927b 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -26,12 +26,19 @@ absl::string_view describePool(const ConnectionPool::Instance& pool) { static constexpr uint32_t kDefaultTimeoutMs = 300; -std::string getSni(const Network::TransportSocketOptionsConstSharedPtr& options, - Network::UpstreamTransportSocketFactory& transport_socket_factory) { +std::string getTargetHostname(const Network::TransportSocketOptionsConstSharedPtr& options, + Upstream::HostConstSharedPtr& host) { if (options && options->serverNameOverride().has_value()) { return options->serverNameOverride().value(); } - return std::string(transport_socket_factory.defaultServerNameIndication()); + std::string default_sni = + std::string(host->transportSocketFactory().defaultServerNameIndication()); + if (!default_sni.empty() || + !Runtime::runtimeFeatureEnabled("envoy.reloadable_features.allow_alt_svc_for_ips")) { + return default_sni; + } + // If there's no configured SNI the hostname is probably an IP address. Return it here. + return host->hostname(); } } // namespace @@ -297,7 +304,7 @@ ConnectivityGrid::ConnectivityGrid( time_source_(time_source), alternate_protocols_(alternate_protocols), quic_stat_names_(quic_stat_names), scope_(scope), // TODO(RyanTheOptimist): Figure out how scheme gets plumbed in here. - origin_("https", getSni(transport_socket_options, host_->transportSocketFactory()), + origin_("https", getTargetHostname(transport_socket_options, host_), host_->address()->ip()->port()), quic_info_(quic_info), priority_(priority) { // ProdClusterManagerFactory::allocateConnPool verifies the protocols are HTTP/1, HTTP/2 and diff --git a/source/common/quic/envoy_quic_proof_verifier.cc b/source/common/quic/envoy_quic_proof_verifier.cc index 0ae8f324eb8b..330da858d75a 100644 --- a/source/common/quic/envoy_quic_proof_verifier.cc +++ b/source/common/quic/envoy_quic_proof_verifier.cc @@ -36,9 +36,10 @@ class QuicValidateResultCallback : public Ssl::ValidateResultCallback { public: QuicValidateResultCallback(Event::Dispatcher& dispatcher, std::unique_ptr&& quic_callback, - const std::string& hostname, const std::string& leaf_cert) + const std::string& hostname, const std::string& leaf_cert, + bool accept_untrusted) : dispatcher_(dispatcher), quic_callback_(std::move(quic_callback)), hostname_(hostname), - leaf_cert_(leaf_cert) {} + leaf_cert_(leaf_cert), accept_untrusted_(accept_untrusted) {} Event::Dispatcher& dispatcher() override { return dispatcher_; } @@ -47,7 +48,8 @@ class QuicValidateResultCallback : public Ssl::ValidateResultCallback { std::string error; if (!succeeded) { error = error_details; - } else { + } else if (!accept_untrusted_ || !Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.extend_h3_accept_untrusted")) { std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); succeeded = verifyLeafCertMatchesHostname(*cert_view, hostname_, &error); @@ -63,6 +65,7 @@ class QuicValidateResultCallback : public Ssl::ValidateResultCallback { const std::string hostname_; // Leaf cert needs to be retained in case of asynchronous validation. std::string leaf_cert_; + const bool accept_untrusted_; }; } // namespace @@ -101,7 +104,7 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( } auto envoy_callback = std::make_unique( - verify_context->dispatcher(), std::move(callback), hostname, certs[0]); + verify_context->dispatcher(), std::move(callback), hostname, certs[0], accept_untrusted_); ASSERT(dynamic_cast(context_.get()) != nullptr); // We down cast rather than add customVerifyCertChainForQuic to Envoy::Ssl::Context because diff --git a/source/common/quic/envoy_quic_proof_verifier.h b/source/common/quic/envoy_quic_proof_verifier.h index b42e9841a709..b5af84ae3c4f 100644 --- a/source/common/quic/envoy_quic_proof_verifier.h +++ b/source/common/quic/envoy_quic_proof_verifier.h @@ -39,8 +39,9 @@ using EnvoyQuicProofVerifyContextPtr = std::unique_ptr QuicClientTransportSocketFactory:: ThreadLocalQuicConfig& tls_config = *tls_slot_; if (tls_config.client_context_ != context) { + bool accept_untrusted = + clientContextConfig() && clientContextConfig()->certificateValidationContext() && + clientContextConfig()->certificateValidationContext()->trustChainVerification() == + envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: + ACCEPT_UNTRUSTED; // If the context has been updated, update the crypto config. tls_config.client_context_ = context; tls_config.crypto_config_ = std::make_shared( - std::make_unique(std::move(context)), + std::make_unique(std::move(context), accept_untrusted), std::make_unique()); } // Return the latest crypto config. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 57c8c7b90657..25cd64d30ded 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -29,6 +29,7 @@ // If issues are found that require a runtime feature to be disabled, it should be reported // ASAP by filing a bug on github. Overriding non-buggy code is strongly discouraged to avoid the // problem of the bugs being found after the old code path has been removed. +RUNTIME_GUARD(envoy_reloadable_features_allow_alt_svc_for_ips); RUNTIME_GUARD(envoy_reloadable_features_check_switch_protocol_websocket_handshake); RUNTIME_GUARD(envoy_reloadable_features_conn_pool_delete_when_idle); RUNTIME_GUARD(envoy_reloadable_features_consistent_header_validation); @@ -44,6 +45,7 @@ RUNTIME_GUARD(envoy_reloadable_features_enable_compression_bomb_protection); RUNTIME_GUARD(envoy_reloadable_features_enable_include_histograms); RUNTIME_GUARD(envoy_reloadable_features_exclude_host_in_eds_status_draining); RUNTIME_GUARD(envoy_reloadable_features_ext_proc_timeout_error); +RUNTIME_GUARD(envoy_reloadable_features_extend_h3_accept_untrusted); RUNTIME_GUARD(envoy_reloadable_features_gcp_authn_use_fixed_url); RUNTIME_GUARD(envoy_reloadable_features_grpc_side_stream_flow_control); RUNTIME_GUARD(envoy_reloadable_features_http1_balsa_delay_reset); diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index 476e6730b9dc..dd14534da4a6 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -4,6 +4,7 @@ #include "source/common/http/conn_pool_grid.h" #include "source/common/http/http_server_properties_cache_impl.h" +#include "source/common/upstream/transport_socket_match_impl.h" #include "test/common/http/common.h" #include "test/common/upstream/utility.h" @@ -51,6 +52,8 @@ class ConnectivityGridForTest : public ConnectivityGrid { return grid.getOrCreateHttp2Pool(); } + std::string getOriginHostname() { return origin_.hostname_; } + ConnectionPool::InstancePtr createHttp3Pool(bool alternate) override { if (!alternate) { return createMockPool("http3"); @@ -177,8 +180,8 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin 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(), + cluster_, host_impl_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_); @@ -213,7 +216,7 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin HttpServerPropertiesCacheImpl::Origin origin_{"https", "hostname", 9000}; const Network::ConnectionSocket::OptionsSharedPtr socket_options_; - const Network::TransportSocketOptionsConstSharedPtr transport_socket_options_; + Network::TransportSocketOptionsConstSharedPtr transport_socket_options_; ConnectivityGrid::ConnectivityOptions options_; Upstream::ClusterConnectivityState state_; std::shared_ptr cluster_{new NiceMock()}; @@ -236,11 +239,40 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin testing::NiceMock thread_local_; NiceMock dispatcher_; std::unique_ptr grid_; + std::string host_impl_hostname_ = "hostname"; }; +TEST_F(ConnectivityGridTest, HostnameFromTransportSocketFactory) { + Network::MockTransportSocketFactory factory; + Upstream::MockTransportSocketMatcher* transport_socket_matcher = + dynamic_cast( + cluster_->transport_socket_matcher_.get()); + EXPECT_CALL(*transport_socket_matcher, resolve(_, _)) + .WillOnce(Return(Upstream::TransportSocketMatcher::MatchData( + factory, transport_socket_matcher->stats_, "test"))); + EXPECT_CALL(factory, defaultServerNameIndication) + .WillRepeatedly(Return("transport_socket_hostname")); + host_impl_hostname_ = "custom_hostname"; + transport_socket_options_ = std::make_shared(); + initialize(); + // Without "hostname" in the TransportSocketOptionsImpl, this fails over to + // the host name in HostNameImpl + EXPECT_EQ("transport_socket_hostname", grid_->getOriginHostname()); +} + +TEST_F(ConnectivityGridTest, NoServerNameOverride) { + host_impl_hostname_ = "custom_hostname"; + transport_socket_options_ = std::make_shared(); + initialize(); + // Without "hostname" in the TransportSocketOptionsImpl, this fails over to + // the host name in HostNameImpl + EXPECT_EQ(host_impl_hostname_, grid_->getOriginHostname()); +} + // Test the first pool successfully connecting. TEST_F(ConnectivityGridTest, Success) { initialize(); + EXPECT_EQ("hostname", grid_->getOriginHostname()); addHttp3AlternateProtocol(); EXPECT_EQ(grid_->http3Pool(), nullptr); EXPECT_NE(grid_->newStream(decoder_, callbacks_,