From e11a6707368fad4426cb8028cd50848fa30453e1 Mon Sep 17 00:00:00 2001 From: Yash Tibrewal Date: Fri, 30 Oct 2020 10:15:57 -0700 Subject: [PATCH] Add XdsCertificateProvider --- BUILD | 6 + BUILD.gn | 3 + CMakeLists.txt | 43 ++ Makefile | 2 + build_autogenerated.yaml | 16 + config.m4 | 1 + config.w32 | 1 + gRPC-C++.podspec | 3 + gRPC-Core.podspec | 4 + grpc.gemspec | 4 + grpc.gyp | 2 + package.xml | 4 + src/core/ext/xds/xds_certificate_provider.cc | 240 ++++++++ src/core/ext/xds/xds_certificate_provider.h | 74 +++ .../tls/grpc_tls_certificate_distributor.cc | 8 +- src/python/grpcio/grpc_core_dependencies.py | 1 + test/core/xds/BUILD | 12 + .../core/xds/xds_certificate_provider_test.cc | 529 ++++++++++++++++++ tools/doxygen/Doxyfile.c++.internal | 2 + tools/doxygen/Doxyfile.core.internal | 2 + tools/run_tests/generated/tests.json | 24 + 21 files changed, 979 insertions(+), 2 deletions(-) create mode 100644 src/core/ext/xds/xds_certificate_provider.cc create mode 100644 src/core/ext/xds/xds_certificate_provider.h create mode 100644 test/core/xds/xds_certificate_provider_test.cc diff --git a/BUILD b/BUILD index 1c5feb70349..2358e2a3dd0 100644 --- a/BUILD +++ b/BUILD @@ -1337,14 +1337,20 @@ grpc_cc_library( srcs = [ "src/core/ext/xds/certificate_provider_registry.cc", "src/core/ext/xds/certificate_provider_store.cc", + "src/core/ext/xds/xds_certificate_provider.cc", "src/core/lib/security/credentials/xds/xds_credentials.cc", ], hdrs = [ "src/core/ext/xds/certificate_provider_factory.h", "src/core/ext/xds/certificate_provider_registry.h", "src/core/ext/xds/certificate_provider_store.h", + "src/core/ext/xds/xds_certificate_provider.h", "src/core/lib/security/credentials/xds/xds_credentials.h", ], + external_deps = [ + "absl/functional:bind_front", + ], + language = "c++", deps = [ "grpc_secure", ], diff --git a/BUILD.gn b/BUILD.gn index 80576ada31f..9de54fe9a7a 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -731,6 +731,8 @@ config("grpc_config") { "src/core/ext/xds/xds_api.h", "src/core/ext/xds/xds_bootstrap.cc", "src/core/ext/xds/xds_bootstrap.h", + "src/core/ext/xds/xds_certificate_provider.cc", + "src/core/ext/xds/xds_certificate_provider.h", "src/core/ext/xds/xds_channel_args.h", "src/core/ext/xds/xds_client.cc", "src/core/ext/xds/xds_client.h", @@ -1207,6 +1209,7 @@ config("grpc_config") { ":absl/types:optional", ":absl/strings:strings", ":absl/status:status", + ":absl/functional:bind_front", ":absl/container:inlined_vector", ":absl/container:flat_hash_set", "//third_party/cares", diff --git a/CMakeLists.txt b/CMakeLists.txt index b2d94ca152a..9a25f4552b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ set(gRPC_ABSL_USED_TARGETS absl_bad_variant_access absl_base absl_base_internal + absl_bind_front absl_bits absl_city absl_civil_time @@ -931,6 +932,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx writes_per_rpc_test) endif() add_dependencies(buildtests_cxx xds_bootstrap_test) + add_dependencies(buildtests_cxx xds_certificate_provider_test) add_dependencies(buildtests_cxx xds_credentials_end2end_test) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx xds_end2end_test) @@ -1701,6 +1703,7 @@ add_library(grpc src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc src/core/ext/xds/xds_api.cc src/core/ext/xds/xds_bootstrap.cc + src/core/ext/xds/xds_certificate_provider.cc src/core/ext/xds/xds_client.cc src/core/ext/xds/xds_client_stats.cc src/core/lib/avl/avl.cc @@ -1992,6 +1995,7 @@ target_link_libraries(grpc absl::optional absl::strings absl::status + absl::bind_front absl::inlined_vector absl::flat_hash_set ) @@ -15248,6 +15252,45 @@ target_link_libraries(xds_bootstrap_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(xds_certificate_provider_test + test/core/xds/xds_certificate_provider_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(xds_certificate_provider_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} +) + +target_link_libraries(xds_certificate_provider_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + grpc_test_util + grpc + gpr + address_sorting + upb + ${_gRPC_GFLAGS_LIBRARIES} +) + + endif() if(gRPC_BUILD_TESTS) diff --git a/Makefile b/Makefile index 6bbd595d594..a63a1891989 100644 --- a/Makefile +++ b/Makefile @@ -2111,6 +2111,7 @@ LIBGRPC_SRC = \ src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc \ src/core/ext/xds/xds_api.cc \ src/core/ext/xds/xds_bootstrap.cc \ + src/core/ext/xds/xds_certificate_provider.cc \ src/core/ext/xds/xds_client.cc \ src/core/ext/xds/xds_client_stats.cc \ src/core/lib/avl/avl.cc \ @@ -4775,6 +4776,7 @@ src/core/ext/xds/certificate_provider_store.cc: $(OPENSSL_DEP) src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc: $(OPENSSL_DEP) src/core/ext/xds/xds_api.cc: $(OPENSSL_DEP) src/core/ext/xds/xds_bootstrap.cc: $(OPENSSL_DEP) +src/core/ext/xds/xds_certificate_provider.cc: $(OPENSSL_DEP) src/core/ext/xds/xds_client.cc: $(OPENSSL_DEP) src/core/ext/xds/xds_client_stats.cc: $(OPENSSL_DEP) src/core/lib/http/httpcli_security_connector.cc: $(OPENSSL_DEP) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index 5bbc7125077..eb99f9a9cba 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -632,6 +632,7 @@ libs: - src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h - src/core/ext/xds/xds_api.h - src/core/ext/xds/xds_bootstrap.h + - src/core/ext/xds/xds_certificate_provider.h - src/core/ext/xds/xds_channel_args.h - src/core/ext/xds/xds_client.h - src/core/ext/xds/xds_client_stats.h @@ -1128,6 +1129,7 @@ libs: - src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc - src/core/ext/xds/xds_api.cc - src/core/ext/xds/xds_bootstrap.cc + - src/core/ext/xds/xds_certificate_provider.cc - src/core/ext/xds/xds_client.cc - src/core/ext/xds/xds_client_stats.cc - src/core/lib/avl/avl.cc @@ -1381,6 +1383,7 @@ libs: - absl/types:optional - absl/strings:strings - absl/status:status + - absl/functional:bind_front - absl/container:inlined_vector - absl/container:flat_hash_set baselib: true @@ -7768,6 +7771,19 @@ targets: - gpr - address_sorting - upb +- name: xds_certificate_provider_test + gtest: true + build: test + language: c++ + headers: [] + src: + - test/core/xds/xds_certificate_provider_test.cc + deps: + - grpc_test_util + - grpc + - gpr + - address_sorting + - upb - name: xds_credentials_end2end_test gtest: true build: test diff --git a/config.m4 b/config.m4 index 5c997ae6bbf..42bf1f5d476 100644 --- a/config.m4 +++ b/config.m4 @@ -315,6 +315,7 @@ if test "$PHP_GRPC" != "no"; then src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc \ src/core/ext/xds/xds_api.cc \ src/core/ext/xds/xds_bootstrap.cc \ + src/core/ext/xds/xds_certificate_provider.cc \ src/core/ext/xds/xds_client.cc \ src/core/ext/xds/xds_client_stats.cc \ src/core/lib/avl/avl.cc \ diff --git a/config.w32 b/config.w32 index 19db387f4b4..9485624f91b 100644 --- a/config.w32 +++ b/config.w32 @@ -282,6 +282,7 @@ if (PHP_GRPC != "no") { "src\\core\\ext\\xds\\google_mesh_ca_certificate_provider_factory.cc " + "src\\core\\ext\\xds\\xds_api.cc " + "src\\core\\ext\\xds\\xds_bootstrap.cc " + + "src\\core\\ext\\xds\\xds_certificate_provider.cc " + "src\\core\\ext\\xds\\xds_client.cc " + "src\\core\\ext\\xds\\xds_client_stats.cc " + "src\\core\\lib\\avl\\avl.cc " + diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index 32da42f26cd..d5af45ef9e0 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -188,6 +188,7 @@ Pod::Spec.new do |s| ss.dependency 'abseil/base/base', abseil_version ss.dependency 'abseil/container/flat_hash_set', abseil_version ss.dependency 'abseil/container/inlined_vector', abseil_version + ss.dependency 'abseil/functional/bind_front', abseil_version ss.dependency 'abseil/memory/memory', abseil_version ss.dependency 'abseil/status/status', abseil_version ss.dependency 'abseil/strings/str_format', abseil_version @@ -448,6 +449,7 @@ Pod::Spec.new do |s| 'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h', 'src/core/ext/xds/xds_api.h', 'src/core/ext/xds/xds_bootstrap.h', + 'src/core/ext/xds/xds_certificate_provider.h', 'src/core/ext/xds/xds_channel_args.h', 'src/core/ext/xds/xds_client.h', 'src/core/ext/xds/xds_client_stats.h', @@ -1049,6 +1051,7 @@ Pod::Spec.new do |s| 'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h', 'src/core/ext/xds/xds_api.h', 'src/core/ext/xds/xds_bootstrap.h', + 'src/core/ext/xds/xds_certificate_provider.h', 'src/core/ext/xds/xds_channel_args.h', 'src/core/ext/xds/xds_client.h', 'src/core/ext/xds/xds_client_stats.h', diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index df61b908832..2175c6066ba 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -177,6 +177,7 @@ Pod::Spec.new do |s| ss.dependency 'abseil/base/base', abseil_version ss.dependency 'abseil/container/flat_hash_set', abseil_version ss.dependency 'abseil/container/inlined_vector', abseil_version + ss.dependency 'abseil/functional/bind_front', abseil_version ss.dependency 'abseil/memory/memory', abseil_version ss.dependency 'abseil/status/status', abseil_version ss.dependency 'abseil/strings/str_format', abseil_version @@ -714,6 +715,8 @@ Pod::Spec.new do |s| 'src/core/ext/xds/xds_api.h', 'src/core/ext/xds/xds_bootstrap.cc', 'src/core/ext/xds/xds_bootstrap.h', + 'src/core/ext/xds/xds_certificate_provider.cc', + 'src/core/ext/xds/xds_certificate_provider.h', 'src/core/ext/xds/xds_channel_args.h', 'src/core/ext/xds/xds_client.cc', 'src/core/ext/xds/xds_client.h', @@ -1575,6 +1578,7 @@ Pod::Spec.new do |s| 'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h', 'src/core/ext/xds/xds_api.h', 'src/core/ext/xds/xds_bootstrap.h', + 'src/core/ext/xds/xds_certificate_provider.h', 'src/core/ext/xds/xds_channel_args.h', 'src/core/ext/xds/xds_client.h', 'src/core/ext/xds/xds_client_stats.h', diff --git a/grpc.gemspec b/grpc.gemspec index 1ffea8b8c05..9d39899c137 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -632,6 +632,8 @@ Gem::Specification.new do |s| s.files += %w( src/core/ext/xds/xds_api.h ) s.files += %w( src/core/ext/xds/xds_bootstrap.cc ) s.files += %w( src/core/ext/xds/xds_bootstrap.h ) + s.files += %w( src/core/ext/xds/xds_certificate_provider.cc ) + s.files += %w( src/core/ext/xds/xds_certificate_provider.h ) s.files += %w( src/core/ext/xds/xds_channel_args.h ) s.files += %w( src/core/ext/xds/xds_client.cc ) s.files += %w( src/core/ext/xds/xds_client.h ) @@ -1269,7 +1271,9 @@ Gem::Specification.new do |s| s.files += %w( third_party/abseil-cpp/absl/debugging/symbolize_elf.inc ) s.files += %w( third_party/abseil-cpp/absl/debugging/symbolize_unimplemented.inc ) s.files += %w( third_party/abseil-cpp/absl/debugging/symbolize_win32.inc ) + s.files += %w( third_party/abseil-cpp/absl/functional/bind_front.h ) s.files += %w( third_party/abseil-cpp/absl/functional/function_ref.h ) + s.files += %w( third_party/abseil-cpp/absl/functional/internal/front_binder.h ) s.files += %w( third_party/abseil-cpp/absl/functional/internal/function_ref.h ) s.files += %w( third_party/abseil-cpp/absl/hash/hash.h ) s.files += %w( third_party/abseil-cpp/absl/hash/internal/city.cc ) diff --git a/grpc.gyp b/grpc.gyp index 8676aad12f3..8699f0fb5b4 100644 --- a/grpc.gyp +++ b/grpc.gyp @@ -443,6 +443,7 @@ 'absl/types:optional', 'absl/strings:strings', 'absl/status:status', + 'absl/functional:bind_front', 'absl/container:inlined_vector', 'absl/container:flat_hash_set', ], @@ -722,6 +723,7 @@ 'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc', 'src/core/ext/xds/xds_api.cc', 'src/core/ext/xds/xds_bootstrap.cc', + 'src/core/ext/xds/xds_certificate_provider.cc', 'src/core/ext/xds/xds_client.cc', 'src/core/ext/xds/xds_client_stats.cc', 'src/core/lib/avl/avl.cc', diff --git a/package.xml b/package.xml index 3468bff34d0..6a2a5df29c2 100644 --- a/package.xml +++ b/package.xml @@ -612,6 +612,8 @@ + + @@ -1271,7 +1273,9 @@ + + diff --git a/src/core/ext/xds/xds_certificate_provider.cc b/src/core/ext/xds/xds_certificate_provider.cc new file mode 100644 index 00000000000..e6ad4dd9134 --- /dev/null +++ b/src/core/ext/xds/xds_certificate_provider.cc @@ -0,0 +1,240 @@ +// +// +// Copyright 2020 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// + +#include + +#include "absl/functional/bind_front.h" +#include "absl/strings/str_cat.h" + +#include "src/core/ext/xds/xds_certificate_provider.h" + +namespace grpc_core { + +namespace { + +class RootCertificatesWatcher + : public grpc_tls_certificate_distributor::TlsCertificatesWatcherInterface { + public: + // Takes a ref to \a parent instead of a raw pointer since the watcher is + // owned by the root certificate distributor and not by \a parent. Note that + // presently, the watcher is immediately deleted when + // CancelTlsCertificatesWatch() is called, but that can potentially change in + // the future. + explicit RootCertificatesWatcher( + RefCountedPtr parent) + : parent_(std::move(parent)) {} + + void OnCertificatesChanged(absl::optional root_certs, + absl::optional + /* key_cert_pairs */) override { + if (root_certs.has_value()) { + parent_->SetKeyMaterials("", std::string(root_certs.value()), + absl::nullopt); + } + } + + void OnError(grpc_error* root_cert_error, + grpc_error* identity_cert_error) override { + if (root_cert_error != GRPC_ERROR_NONE) { + parent_->SetErrorForCert("", root_cert_error /* pass the ref */, + absl::nullopt); + } + GRPC_ERROR_UNREF(identity_cert_error); + } + + private: + RefCountedPtr parent_; +}; + +class IdentityCertificatesWatcher + : public grpc_tls_certificate_distributor::TlsCertificatesWatcherInterface { + public: + // Takes a ref to \a parent instead of a raw pointer since the watcher is + // owned by the root certificate distributor and not by \a parent. Note that + // presently, the watcher is immediately deleted when + // CancelTlsCertificatesWatch() is called, but that can potentially change in + // the future. + explicit IdentityCertificatesWatcher( + RefCountedPtr parent) + : parent_(std::move(parent)) {} + + void OnCertificatesChanged( + absl::optional /* root_certs */, + absl::optional key_cert_pairs) override { + if (key_cert_pairs.has_value()) { + parent_->SetKeyMaterials("", absl::nullopt, key_cert_pairs); + } + } + + void OnError(grpc_error* root_cert_error, + grpc_error* identity_cert_error) override { + if (identity_cert_error != GRPC_ERROR_NONE) { + parent_->SetErrorForCert("", absl::nullopt, + identity_cert_error /* pass the ref */); + } + GRPC_ERROR_UNREF(root_cert_error); + } + + private: + RefCountedPtr parent_; +}; + +} // namespace + +XdsCertificateProvider::XdsCertificateProvider( + absl::string_view root_cert_name, + RefCountedPtr root_cert_distributor, + absl::string_view identity_cert_name, + RefCountedPtr identity_cert_distributor) + : root_cert_name_(root_cert_name), + identity_cert_name_(identity_cert_name), + root_cert_distributor_(std::move(root_cert_distributor)), + identity_cert_distributor_(std::move(identity_cert_distributor)), + distributor_(MakeRefCounted()) { + distributor_->SetWatchStatusCallback( + absl::bind_front(&XdsCertificateProvider::WatchStatusCallback, this)); +} + +void XdsCertificateProvider::UpdateRootCertNameAndDistributor( + absl::string_view root_cert_name, + RefCountedPtr root_cert_distributor) { + MutexLock lock(&mu_); + root_cert_name_ = std::string(root_cert_name); + if (watching_root_certs_) { + // The root certificates are being watched. Swap out the watcher. + if (root_cert_distributor_ != nullptr) { + root_cert_distributor_->CancelTlsCertificatesWatch(root_cert_watcher_); + } + if (root_cert_distributor != nullptr) { + UpdateRootCertWatcher(root_cert_distributor.get()); + } else { + root_cert_watcher_ = nullptr; + distributor_->SetErrorForCert( + "", + GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "No certificate provider available for root certificates"), + absl::nullopt); + } + } + // Swap out the root certificate distributor + root_cert_distributor_ = std::move(root_cert_distributor); +} + +void XdsCertificateProvider::UpdateIdentityCertNameAndDistributor( + absl::string_view identity_cert_name, + RefCountedPtr identity_cert_distributor) { + MutexLock lock(&mu_); + identity_cert_name_ = std::string(identity_cert_name); + if (watching_identity_certs_) { + // The identity certificates are being watched. Swap out the watcher. + if (identity_cert_distributor_ != nullptr) { + identity_cert_distributor_->CancelTlsCertificatesWatch( + identity_cert_watcher_); + } + if (identity_cert_distributor != nullptr) { + UpdateIdentityCertWatcher(identity_cert_distributor.get()); + } else { + identity_cert_watcher_ = nullptr; + distributor_->SetErrorForCert( + "", absl::nullopt, + GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "No certificate provider available for identity certificates")); + } + } + // Swap out the identity certificate distributor + identity_cert_distributor_ = std::move(identity_cert_distributor); +} + +void XdsCertificateProvider::WatchStatusCallback(std::string cert_name, + bool root_being_watched, + bool identity_being_watched) { + // We aren't specially handling the case where root_cert_distributor is same + // as identity_cert_distributor. Always using two separate watchers + // irrespective of the fact results in a straightforward design, and using a + // single watcher does not seem to provide any benefit other than cutting down + // on the number of callbacks. + MutexLock lock(&mu_); + if (!cert_name.empty()) { + grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("Illegal certificate name: \'", cert_name, + "\'. Should be empty.") + .c_str()); + distributor_->SetErrorForCert(cert_name, GRPC_ERROR_REF(error), + GRPC_ERROR_REF(error)); + GRPC_ERROR_UNREF(error); + return; + } + if (root_being_watched && !watching_root_certs_) { + // We need to start watching root certs. + watching_root_certs_ = true; + if (root_cert_distributor_ == nullptr) { + distributor_->SetErrorForCert( + "", + GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "No certificate provider available for root certificates"), + absl::nullopt); + } else { + UpdateRootCertWatcher(root_cert_distributor_.get()); + } + } else if (!root_being_watched && watching_root_certs_) { + // We need to cancel root certs watch. + watching_root_certs_ = false; + if (root_cert_distributor_ != nullptr) { + root_cert_distributor_->CancelTlsCertificatesWatch(root_cert_watcher_); + root_cert_watcher_ = nullptr; + } + GPR_ASSERT(root_cert_watcher_ == nullptr); + } + if (identity_being_watched && !watching_identity_certs_) { + watching_identity_certs_ = true; + if (identity_cert_distributor_ == nullptr) { + distributor_->SetErrorForCert( + "", absl::nullopt, + GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "No certificate provider available for identity certificates")); + } else { + UpdateIdentityCertWatcher(identity_cert_distributor_.get()); + } + } else if (!identity_being_watched && watching_identity_certs_) { + watching_identity_certs_ = false; + if (identity_cert_distributor_ != nullptr) { + identity_cert_distributor_->CancelTlsCertificatesWatch( + identity_cert_watcher_); + identity_cert_watcher_ = nullptr; + } + GPR_ASSERT(identity_cert_watcher_ == nullptr); + } +} + +void XdsCertificateProvider::UpdateRootCertWatcher( + grpc_tls_certificate_distributor* root_cert_distributor) { + auto watcher = absl::make_unique(distributor()); + root_cert_watcher_ = watcher.get(); + root_cert_distributor->WatchTlsCertificates(std::move(watcher), + root_cert_name_, absl::nullopt); +} + +void XdsCertificateProvider::UpdateIdentityCertWatcher( + grpc_tls_certificate_distributor* identity_cert_distributor) { + auto watcher = absl::make_unique(distributor()); + identity_cert_watcher_ = watcher.get(); + identity_cert_distributor->WatchTlsCertificates( + std::move(watcher), absl::nullopt, identity_cert_name_); +} + +} // namespace grpc_core diff --git a/src/core/ext/xds/xds_certificate_provider.h b/src/core/ext/xds/xds_certificate_provider.h new file mode 100644 index 00000000000..caf0e5cc8ae --- /dev/null +++ b/src/core/ext/xds/xds_certificate_provider.h @@ -0,0 +1,74 @@ +// +// +// Copyright 2020 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// + +#ifndef GRPC_CORE_EXT_XDS_XDS_CERTIFICATE_PROVIDER_H +#define GRPC_CORE_EXT_XDS_XDS_CERTIFICATE_PROVIDER_H + +#include + +#include "src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.h" + +namespace grpc_core { + +class XdsCertificateProvider : public grpc_tls_certificate_provider { + public: + XdsCertificateProvider( + absl::string_view root_cert_name, + RefCountedPtr root_cert_distributor, + absl::string_view identity_cert_name, + RefCountedPtr + identity_cert_distributor); + + void UpdateRootCertNameAndDistributor( + absl::string_view root_cert_name, + RefCountedPtr root_cert_distributor); + void UpdateIdentityCertNameAndDistributor( + absl::string_view identity_cert_name, + RefCountedPtr + identity_cert_distributor); + + grpc_core::RefCountedPtr distributor() + const override { + return distributor_; + } + + private: + void WatchStatusCallback(std::string cert_name, bool root_being_watched, + bool identity_being_watched); + void UpdateRootCertWatcher( + grpc_tls_certificate_distributor* root_cert_distributor); + void UpdateIdentityCertWatcher( + grpc_tls_certificate_distributor* identity_cert_distributor); + + Mutex mu_; + bool watching_root_certs_ = false; + bool watching_identity_certs_ = false; + std::string root_cert_name_; + std::string identity_cert_name_; + RefCountedPtr root_cert_distributor_; + RefCountedPtr identity_cert_distributor_; + RefCountedPtr distributor_; + grpc_tls_certificate_distributor::TlsCertificatesWatcherInterface* + root_cert_watcher_ = nullptr; + grpc_tls_certificate_distributor::TlsCertificatesWatcherInterface* + identity_cert_watcher_ = nullptr; +}; + +} // namespace grpc_core + +#endif // GRPC_CORE_EXT_XDS_XDS_CERTIFICATE_PROVIDER_H diff --git a/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc b/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc index fb6d2e967ea..2aee16575b4 100644 --- a/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc +++ b/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc @@ -46,7 +46,9 @@ void grpc_tls_certificate_distributor::SetKeyMaterials( } else if (watcher_it->second.identity_cert_name.has_value()) { auto& identity_cert_info = certificate_info_map_[*watcher_it->second.identity_cert_name]; - pem_key_cert_pairs_to_report = identity_cert_info.pem_key_cert_pairs; + if (!identity_cert_info.pem_key_cert_pairs.empty()) { + pem_key_cert_pairs_to_report = identity_cert_info.pem_key_cert_pairs; + } } watcher_ptr->OnCertificatesChanged( pem_root_certs, std::move(pem_key_cert_pairs_to_report)); @@ -70,7 +72,9 @@ void grpc_tls_certificate_distributor::SetKeyMaterials( } else if (watcher_it->second.root_cert_name.has_value()) { auto& root_cert_info = certificate_info_map_[*watcher_it->second.root_cert_name]; - pem_root_certs_to_report = root_cert_info.pem_root_certs; + if (!root_cert_info.pem_root_certs.empty()) { + pem_root_certs_to_report = root_cert_info.pem_root_certs; + } } watcher_ptr->OnCertificatesChanged(pem_root_certs_to_report, pem_key_cert_pairs); diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py index 1c2ace2e09d..0fc2e5ffb30 100644 --- a/src/python/grpcio/grpc_core_dependencies.py +++ b/src/python/grpcio/grpc_core_dependencies.py @@ -291,6 +291,7 @@ CORE_SOURCE_FILES = [ 'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc', 'src/core/ext/xds/xds_api.cc', 'src/core/ext/xds/xds_bootstrap.cc', + 'src/core/ext/xds/xds_certificate_provider.cc', 'src/core/ext/xds/xds_client.cc', 'src/core/ext/xds/xds_client_stats.cc', 'src/core/lib/avl/avl.cc', diff --git a/test/core/xds/BUILD b/test/core/xds/BUILD index a06b7a01836..6ddd10d2b3f 100644 --- a/test/core/xds/BUILD +++ b/test/core/xds/BUILD @@ -55,3 +55,15 @@ grpc_cc_test( "//test/core/util:grpc_test_util", ], ) + +grpc_cc_test( + name = "xds_certificate_provider_test", + srcs = ["xds_certificate_provider_test.cc"], + external_deps = ["gtest"], + language = "C++", + deps = [ + "//:gpr", + "//:grpc", + "//test/core/util:grpc_test_util", + ], +) diff --git a/test/core/xds/xds_certificate_provider_test.cc b/test/core/xds/xds_certificate_provider_test.cc new file mode 100644 index 00000000000..25dc7aeb665 --- /dev/null +++ b/test/core/xds/xds_certificate_provider_test.cc @@ -0,0 +1,529 @@ +// +// +// Copyright 2020 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// + +#include +#include + +#include "src/core/ext/xds/xds_certificate_provider.h" +#include "test/core/util/test_config.h" + +namespace grpc_core { +namespace testing { +namespace { + +constexpr const char* kRootCert1 = "root_cert_1_contents"; +constexpr const char* kRootCert2 = "root_cert_2_contents"; +constexpr const char* kIdentityCert1PrivateKey = "identity_private_key_1"; +constexpr const char* kIdentityCert1 = "identity_cert_1_contents"; +constexpr const char* kIdentityCert2PrivateKey = "identity_private_key_2"; +constexpr const char* kIdentityCert2 = "identity_cert_2_contents"; +constexpr const char* kRootErrorMessage = "root_error_message"; +constexpr const char* kIdentityErrorMessage = "identity_error_message"; + +PemKeyCertPairList MakeKeyCertPairs(const char* private_key, + const char* certs) { + if (strcmp(private_key, "") == 0 && strcmp(certs, "") == 0) { + return {}; + } + grpc_ssl_pem_key_cert_pair* ssl_pair = + static_cast( + gpr_malloc(sizeof(grpc_ssl_pem_key_cert_pair))); + ssl_pair->private_key = gpr_strdup(private_key); + ssl_pair->cert_chain = gpr_strdup(certs); + return PemKeyCertPairList{PemKeyCertPair(ssl_pair)}; +} + +PemKeyCertPairList MakeKeyCertPairsType1() { + return MakeKeyCertPairs(kIdentityCert1PrivateKey, kIdentityCert1); +} + +PemKeyCertPairList MakeKeyCertPairsType2() { + return MakeKeyCertPairs(kIdentityCert2PrivateKey, kIdentityCert2); +} + +class TestCertificatesWatcher + : public grpc_tls_certificate_distributor::TlsCertificatesWatcherInterface { + public: + ~TestCertificatesWatcher() override { + GRPC_ERROR_UNREF(root_cert_error_); + GRPC_ERROR_UNREF(identity_cert_error_); + } + + void OnCertificatesChanged( + absl::optional root_certs, + absl::optional key_cert_pairs) override { + if (root_certs.has_value()) { + if (!root_certs_.has_value() || + (root_certs_.has_value() && + std::string(root_certs.value()) != root_certs_.value())) { + GRPC_ERROR_UNREF(root_cert_error_); + root_cert_error_ = GRPC_ERROR_NONE; + } + root_certs_.emplace(std::string(root_certs.value())); + } + if (key_cert_pairs.has_value()) { + if (key_cert_pairs != key_cert_pairs_) { + GRPC_ERROR_UNREF(identity_cert_error_); + identity_cert_error_ = GRPC_ERROR_NONE; + key_cert_pairs_ = key_cert_pairs; + } + } + } + + void OnError(grpc_error* root_cert_error, + grpc_error* identity_cert_error) override { + GRPC_ERROR_UNREF(root_cert_error_); + root_cert_error_ = root_cert_error; + GRPC_ERROR_UNREF(identity_cert_error_); + identity_cert_error_ = identity_cert_error; + } + + const absl::optional& root_certs() const { return root_certs_; } + + const absl::optional& key_cert_pairs() const { + return key_cert_pairs_; + } + + grpc_error* root_cert_error() const { return root_cert_error_; } + + grpc_error* identity_cert_error() const { return identity_cert_error_; } + + private: + absl::optional root_certs_; + absl::optional key_cert_pairs_; + grpc_error* root_cert_error_ = GRPC_ERROR_NONE; + grpc_error* identity_cert_error_ = GRPC_ERROR_NONE; +}; + +TEST( + XdsCertificateProviderTest, + RootCertDistributorDifferentFromIdentityCertDistributorDifferentCertNames) { + auto root_cert_distributor = + MakeRefCounted(); + auto identity_cert_distributor = + MakeRefCounted(); + XdsCertificateProvider provider("root", root_cert_distributor, "identity", + identity_cert_distributor); + auto* watcher = new TestCertificatesWatcher; + provider.distributor()->WatchTlsCertificates( + std::unique_ptr(watcher), "", ""); + EXPECT_EQ(watcher->root_certs(), absl::nullopt); + EXPECT_EQ(watcher->key_cert_pairs(), absl::nullopt); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Update both root certs and identity certs + root_cert_distributor->SetKeyMaterials("root", kRootCert1, absl::nullopt); + identity_cert_distributor->SetKeyMaterials("identity", absl::nullopt, + MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Second update for just root certs + root_cert_distributor->SetKeyMaterials( + "root", kRootCert2, + MakeKeyCertPairsType2() /* does not have an effect */); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Second update for identity certs + identity_cert_distributor->SetKeyMaterials( + "identity", kRootCert1 /* does not have an effect */, + MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Set error for both root and identity + root_cert_distributor->SetErrorForCert( + "root", GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), + absl::nullopt); + identity_cert_distributor->SetErrorForCert( + "identity", absl::nullopt, + GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr(kRootErrorMessage)); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr(kIdentityErrorMessage)); + // Send an update for root certs. Test that the root cert error is reset. + root_cert_distributor->SetKeyMaterials("root", kRootCert1, absl::nullopt); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr(kIdentityErrorMessage)); + // Send an update for identity certs. Test that the identity cert error is + // reset. + identity_cert_distributor->SetKeyMaterials("identity", absl::nullopt, + MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); +} + +TEST(XdsCertificateProviderTest, + RootCertDistributorDifferentFromIdentityCertDistributorSameCertNames) { + auto root_cert_distributor = + MakeRefCounted(); + auto identity_cert_distributor = + MakeRefCounted(); + XdsCertificateProvider provider("test", root_cert_distributor, "test", + identity_cert_distributor); + auto* watcher = new TestCertificatesWatcher; + provider.distributor()->WatchTlsCertificates( + std::unique_ptr(watcher), "", ""); + EXPECT_EQ(watcher->root_certs(), absl::nullopt); + EXPECT_EQ(watcher->key_cert_pairs(), absl::nullopt); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Update both root certs and identity certs + root_cert_distributor->SetKeyMaterials("test", kRootCert1, absl::nullopt); + identity_cert_distributor->SetKeyMaterials("test", absl::nullopt, + MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Second update for just root certs + root_cert_distributor->SetKeyMaterials("test", kRootCert2, absl::nullopt); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Second update for identity certs + identity_cert_distributor->SetKeyMaterials("test", absl::nullopt, + MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Set error for both root and identity + root_cert_distributor->SetErrorForCert( + "test", GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), + absl::nullopt); + identity_cert_distributor->SetErrorForCert( + "test", absl::nullopt, + GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr(kRootErrorMessage)); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr(kIdentityErrorMessage)); + // Send an update for root certs. Test that the root cert error is reset. + root_cert_distributor->SetKeyMaterials("test", kRootCert1, absl::nullopt); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr(kIdentityErrorMessage)); + // Send an update for identity certs. Test that the identity cert error is + // reset. + identity_cert_distributor->SetKeyMaterials("test", absl::nullopt, + MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Test update on unwatched cert name + identity_cert_distributor->SetKeyMaterials("identity", kRootCert2, + MakeKeyCertPairsType2()); + root_cert_distributor->SetKeyMaterials("root", kRootCert1, + MakeKeyCertPairsType1()); +} + +TEST(XdsCertificateProviderTest, + RootCertDistributorSameAsIdentityCertDistributorDifferentCertNames) { + auto distributor = MakeRefCounted(); + XdsCertificateProvider provider("root", distributor, "identity", distributor); + auto* watcher = new TestCertificatesWatcher; + provider.distributor()->WatchTlsCertificates( + std::unique_ptr(watcher), "", ""); + EXPECT_EQ(watcher->root_certs(), absl::nullopt); + EXPECT_EQ(watcher->key_cert_pairs(), absl::nullopt); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Update both root certs and identity certs + distributor->SetKeyMaterials("root", kRootCert1, MakeKeyCertPairsType2()); + distributor->SetKeyMaterials("identity", kRootCert2, MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Second update for just root certs + distributor->SetKeyMaterials("root", kRootCert2, MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Second update for identity certs + distributor->SetKeyMaterials("identity", kRootCert1, MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Set error for root + distributor->SetErrorForCert( + "root", GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), + GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage)); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr(kRootErrorMessage)); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + distributor->SetErrorForCert( + "identity", GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage), + GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr(kRootErrorMessage)); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr(kIdentityErrorMessage)); + // Send an update for root + distributor->SetKeyMaterials("root", kRootCert1, MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr(kIdentityErrorMessage)); + // Send an update for identity + distributor->SetKeyMaterials("identity", kRootCert2, MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); +} + +TEST(XdsCertificateProviderTest, + RootCertDistributorSameAsIdentityCertDistributorSameCertNames) { + auto distributor = MakeRefCounted(); + XdsCertificateProvider provider("", distributor, "", distributor); + auto* watcher = new TestCertificatesWatcher; + provider.distributor()->WatchTlsCertificates( + std::unique_ptr(watcher), "", ""); + EXPECT_EQ(watcher->root_certs(), absl::nullopt); + EXPECT_EQ(watcher->key_cert_pairs(), absl::nullopt); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Update both root certs and identity certs + distributor->SetKeyMaterials("", kRootCert1, MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Second update for just root certs + distributor->SetKeyMaterials("", kRootCert2, absl::nullopt); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Second update for identity certs + distributor->SetKeyMaterials("", absl::nullopt, MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Set error for root + distributor->SetErrorForCert( + "", GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), + absl::nullopt); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr(kRootErrorMessage)); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Set error for identity + distributor->SetErrorForCert( + "", absl::nullopt, + GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr(kRootErrorMessage)); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr(kIdentityErrorMessage)); + // Send an update for root + distributor->SetKeyMaterials("", kRootCert1, absl::nullopt); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr(kIdentityErrorMessage)); + // Send an update for identity + distributor->SetKeyMaterials("", absl::nullopt, MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); +} + +TEST(XdsCertificateProviderTest, SwapOutDistributorsMultipleTimes) { + auto distributor = MakeRefCounted(); + distributor->SetKeyMaterials("", kRootCert1, MakeKeyCertPairsType1()); + XdsCertificateProvider provider("", nullptr, "", nullptr); + auto* watcher = new TestCertificatesWatcher; + provider.distributor()->WatchTlsCertificates( + std::unique_ptr(watcher), "", ""); + // Initially there are no certificate providers. + EXPECT_EQ(watcher->root_certs(), absl::nullopt); + EXPECT_EQ(watcher->key_cert_pairs(), absl::nullopt); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr( + "No certificate provider available for root certificates")); + EXPECT_THAT( + grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr( + "No certificate provider available for identity certificates")); + // Update root cert distributor. + provider.UpdateRootCertNameAndDistributor("", distributor); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), absl::nullopt); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_THAT( + grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr( + "No certificate provider available for identity certificates")); + // Update identity cert distributor + provider.UpdateIdentityCertNameAndDistributor("", distributor); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Update both root and identity certs + distributor->SetKeyMaterials("", kRootCert2, MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Set error for both root and identity + distributor->SetErrorForCert( + "", GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), + GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr(kRootErrorMessage)); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr(kIdentityErrorMessage)); + // Send an update again + distributor->SetKeyMaterials("", kRootCert1, MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Remove root cert provider + provider.UpdateRootCertNameAndDistributor("", nullptr); + distributor->SetKeyMaterials("", kRootCert2, MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); // not updated + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr( + "No certificate provider available for root certificates")); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Remove identity cert provider too + provider.UpdateIdentityCertNameAndDistributor("", nullptr); + distributor->SetKeyMaterials("", kRootCert1, MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); // not updated + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr( + "No certificate provider available for root certificates")); + EXPECT_THAT( + grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr( + "No certificate provider available for identity certificates")); + // Change certificate names being watched, without any certificate updates. + provider.UpdateRootCertNameAndDistributor("root", distributor); + provider.UpdateIdentityCertNameAndDistributor("identity", distributor); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr( + "No certificate provider available for root certificates")); + EXPECT_THAT( + grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr( + "No certificate provider available for identity certificates")); + // Send out certificate updates. + distributor->SetKeyMaterials("root", kRootCert2, absl::nullopt); + distributor->SetKeyMaterials("identity", absl::nullopt, + MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Swap in new certificate distributors with different certificate names and + // existing updates. + auto root_cert_distributor = + MakeRefCounted(); + auto identity_cert_distributor = + MakeRefCounted(); + provider.UpdateRootCertNameAndDistributor("root", root_cert_distributor); + provider.UpdateIdentityCertNameAndDistributor("identity", + identity_cert_distributor); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Change certificate names without any certificate updates. + provider.UpdateRootCertNameAndDistributor("test", root_cert_distributor); + provider.UpdateIdentityCertNameAndDistributor("test", + identity_cert_distributor); + EXPECT_EQ(watcher->root_certs(), kRootCert2); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType1()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); + // Send out certificate updates. + root_cert_distributor->SetKeyMaterials("test", kRootCert1, + MakeKeyCertPairsType1()); + identity_cert_distributor->SetKeyMaterials("test", kRootCert2, + MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_certs(), kRootCert1); + EXPECT_EQ(watcher->key_cert_pairs(), MakeKeyCertPairsType2()); + EXPECT_EQ(watcher->root_cert_error(), GRPC_ERROR_NONE); + EXPECT_EQ(watcher->identity_cert_error(), GRPC_ERROR_NONE); +} + +TEST(XdsCertificateProviderTest, CertificateNameNotEmpty) { + XdsCertificateProvider provider("", nullptr, "", nullptr); + auto* watcher = new TestCertificatesWatcher; + provider.distributor()->WatchTlsCertificates( + std::unique_ptr(watcher), "test", "test"); + EXPECT_THAT(grpc_error_string(watcher->root_cert_error()), + ::testing::HasSubstr("Illegal certificate name: \'test\'")); + EXPECT_THAT(grpc_error_string(watcher->identity_cert_error()), + ::testing::HasSubstr("Illegal certificate name: \'test\'")); +} + +} // namespace +} // namespace testing +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + grpc::testing::TestEnvironment env(argc, argv); + grpc_init(); + auto result = RUN_ALL_TESTS(); + grpc_shutdown(); + return result; +} diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal index 5a829b2e0ae..cf1ad97bf19 100644 --- a/tools/doxygen/Doxyfile.c++.internal +++ b/tools/doxygen/Doxyfile.c++.internal @@ -1564,6 +1564,8 @@ src/core/ext/xds/xds_api.cc \ src/core/ext/xds/xds_api.h \ src/core/ext/xds/xds_bootstrap.cc \ src/core/ext/xds/xds_bootstrap.h \ +src/core/ext/xds/xds_certificate_provider.cc \ +src/core/ext/xds/xds_certificate_provider.h \ src/core/ext/xds/xds_channel_args.h \ src/core/ext/xds/xds_client.cc \ src/core/ext/xds/xds_client.h \ diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index 5f62faf0370..d5a14e7e48f 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -1401,6 +1401,8 @@ src/core/ext/xds/xds_api.cc \ src/core/ext/xds/xds_api.h \ src/core/ext/xds/xds_bootstrap.cc \ src/core/ext/xds/xds_bootstrap.h \ +src/core/ext/xds/xds_certificate_provider.cc \ +src/core/ext/xds/xds_certificate_provider.h \ src/core/ext/xds/xds_channel_args.h \ src/core/ext/xds/xds_client.cc \ src/core/ext/xds/xds_client.h \ diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index 83827843f40..2c1bf4643e7 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -6163,6 +6163,30 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "xds_certificate_provider_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": true + }, { "args": [], "benchmark": false,