diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 7eb786d7ab34..37bf08578226 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -20,5 +20,10 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: google_grpc + change: | + Added an off-by-default runtime flag + ``envoy.reloadable_features.google_grpc_disable_tls_13`` to disable TLSv1.3 + usage by gRPC SDK for ``google_grpc`` services. deprecated: diff --git a/source/common/grpc/BUILD b/source/common/grpc/BUILD index 96d1ed06353d..d662c6863e4b 100644 --- a/source/common/grpc/BUILD +++ b/source/common/grpc/BUILD @@ -210,6 +210,7 @@ envoy_cc_library( "//envoy/grpc:google_grpc_creds_interface", "//envoy/registry", "//source/common/config:datasource_lib", + "//source/common/runtime:runtime_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], alwayslink = LEGACY_ALWAYSLINK, diff --git a/source/common/grpc/google_grpc_creds_impl.cc b/source/common/grpc/google_grpc_creds_impl.cc index ae49d3257a7f..5aa2ea91fd8a 100644 --- a/source/common/grpc/google_grpc_creds_impl.cc +++ b/source/common/grpc/google_grpc_creds_impl.cc @@ -4,6 +4,9 @@ #include "envoy/grpc/google_grpc_creds.h" #include "source/common/config/datasource.h" +#include "source/common/runtime/runtime_features.h" + +#include "grpcpp/security/tls_certificate_provider.h" namespace Envoy { namespace Grpc { @@ -15,12 +18,29 @@ std::shared_ptr CredsUtility::getChannelCredentials( case envoy::config::core::v3::GrpcService::GoogleGrpc::ChannelCredentials:: CredentialSpecifierCase::kSslCredentials: { const auto& ssl_credentials = google_grpc.channel_credentials().ssl_credentials(); - const grpc::SslCredentialsOptions ssl_credentials_options = { - Config::DataSource::read(ssl_credentials.root_certs(), true, api), - Config::DataSource::read(ssl_credentials.private_key(), true, api), - Config::DataSource::read(ssl_credentials.cert_chain(), true, api), - }; - return grpc::SslCredentials(ssl_credentials_options); + const auto root_certs = Config::DataSource::read(ssl_credentials.root_certs(), true, api); + const auto private_key = Config::DataSource::read(ssl_credentials.private_key(), true, api); + const auto cert_chain = Config::DataSource::read(ssl_credentials.cert_chain(), true, api); + grpc::experimental::TlsChannelCredentialsOptions options; + if (!private_key.empty() || !cert_chain.empty()) { + options.set_certificate_provider( + std::make_shared( + root_certs, + std::vector{{private_key, cert_chain}})); + } else if (!root_certs.empty()) { + options.set_certificate_provider( + std::make_shared(root_certs)); + } + if (!root_certs.empty()) { + options.watch_root_certs(); + } + if (!private_key.empty() || !cert_chain.empty()) { + options.watch_identity_key_cert_pairs(); + } + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.google_grpc_disable_tls_13")) { + options.set_max_tls_version(grpc_tls_version::TLS1_2); + } + return grpc::experimental::TlsCredentials(options); } case envoy::config::core::v3::GrpcService::GoogleGrpc::ChannelCredentials:: CredentialSpecifierCase::kLocalCredentials: { @@ -43,7 +63,11 @@ std::shared_ptr CredsUtility::defaultSslChannelCredent if (creds != nullptr) { return creds; } - return grpc::SslCredentials({}); + grpc::experimental::TlsChannelCredentialsOptions options; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.google_grpc_disable_tls_13")) { + options.set_max_tls_version(grpc_tls_version::TLS1_2); + } + return grpc::experimental::TlsCredentials(options); } std::vector> diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 0cebdf40afff..84214f3717e5 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -132,6 +132,10 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_no_downgrade_to_canonical_name); // TODO(pksohn): enable after fixing https://github.com/envoyproxy/envoy/issues/29930 FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_defer_logging_to_ack_listener); +// A flag to set the maximum TLS version for google_grpc client to TLS1.2, when needed for +// compliance restrictions. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_google_grpc_disable_tls_13); + // Block of non-boolean flags. Use of int flags is deprecated. Do not add more. ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT ABSL_FLAG(uint64_t, re2_max_program_size_warn_level, // NOLINT diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index 01458712de95..890b2ba26d61 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -178,6 +178,7 @@ envoy_cc_test( ":grpc_client_integration_test_harness_lib", "//source/common/grpc:async_client_lib", "//source/extensions/grpc_credentials/example:config", + "//test/test_common:test_runtime_lib", ] + envoy_select_google_grpc(["//source/common/grpc:google_async_client_lib"]), ) diff --git a/test/common/grpc/grpc_client_integration_test.cc b/test/common/grpc/grpc_client_integration_test.cc index 8cf420e1e1a1..f3fe4bea09ac 100644 --- a/test/common/grpc/grpc_client_integration_test.cc +++ b/test/common/grpc/grpc_client_integration_test.cc @@ -5,6 +5,7 @@ #endif +#include "test/test_common/test_runtime.h" #include "test/common/grpc/grpc_client_integration_test_harness.h" using testing::Eq; @@ -499,6 +500,29 @@ TEST_P(GrpcSslClientIntegrationTest, BasicSslRequestWithClientCert) { dispatcher_helper_.runDispatcher(); } +// Validate TLS version mismatch between the client and the server. +TEST_P(GrpcSslClientIntegrationTest, BasicSslRequestHandshakeFailure) { + SKIP_IF_GRPC_CLIENT(ClientType::EnvoyGrpc); + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.google_grpc_disable_tls_13", "true"}}); + use_server_tls_13_ = true; + initialize(); + auto request = createRequest(empty_metadata_, false); + EXPECT_CALL(*request->child_span_, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("13"))); + EXPECT_CALL(*request->child_span_, + setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + EXPECT_CALL(*request, onFailure(Status::Internal, "", _)).WillOnce(InvokeWithoutArgs([this]() { + dispatcher_helper_.dispatcher_.exit(); + })); + EXPECT_CALL(*request->child_span_, finishSpan()); + FakeRawConnectionPtr fake_connection; + ASSERT_TRUE(fake_upstream_->waitForRawConnection(fake_connection)); + if (fake_connection->connected()) { + ASSERT_TRUE(fake_connection->waitForDisconnect()); + } + dispatcher_helper_.dispatcher_.run(Event::Dispatcher::RunType::Block); +} + #ifdef ENVOY_GOOGLE_GRPC // AccessToken credential validation tests. class GrpcAccessTokenClientIntegrationTest : public GrpcSslClientIntegrationTest { diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index 8b5a9c444227..799bd4f8da4b 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -394,7 +394,8 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { virtual void expectExtraHeaders(FakeStream&) {} - HelloworldRequestPtr createRequest(const TestMetadata& initial_metadata) { + HelloworldRequestPtr createRequest(const TestMetadata& initial_metadata, + bool expect_upstream_request = true) { auto request = std::make_unique(dispatcher_helper_); EXPECT_CALL(*request, onCreateInitialMetadata(_)) .WillOnce(Invoke([&initial_metadata](Http::HeaderMap& headers) { @@ -421,6 +422,10 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { active_span, Http::AsyncClient::RequestOptions()); EXPECT_NE(request->grpc_request_, nullptr); + if (!expect_upstream_request) { + return request; + } + if (!fake_connection_) { AssertionResult result = fake_upstream_->waitForHttpConnection(*dispatcher_, fake_connection_); @@ -556,6 +561,7 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { tls_cert->mutable_private_key()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/clientkey.pem")); } + auto cfg = std::make_unique( tls_context, factory_context_); @@ -587,6 +593,13 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { validation_context->mutable_trusted_ca()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); } + if (use_server_tls_13_) { + auto* tls_params = common_tls_context->mutable_tls_params(); + tls_params->set_tls_minimum_protocol_version( + envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3); + tls_params->set_tls_maximum_protocol_version( + envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3); + } auto cfg = std::make_unique( tls_context, factory_context_); @@ -598,6 +611,7 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { } bool use_client_cert_{}; + bool use_server_tls_13_{false}; testing::NiceMock factory_context_; };