diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bfb16b249d..5d07b41954c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -830,6 +830,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx google_mesh_ca_certificate_provider_factory_test) add_dependencies(buildtests_cxx grpc_cli) add_dependencies(buildtests_cxx grpc_tls_certificate_distributor_test) + add_dependencies(buildtests_cxx grpc_tls_certificate_provider_test) add_dependencies(buildtests_cxx grpc_tls_credentials_options_test) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx grpc_tool_test) @@ -11696,6 +11697,7 @@ if(gRPC_BUILD_TESTS) add_executable(grpc_tls_certificate_distributor_test test/core/security/grpc_tls_certificate_distributor_test.cc + test/core/security/tls_utils.cc third_party/googletest/googletest/src/gtest-all.cc third_party/googletest/googlemock/src/gmock-all.cc ) @@ -11729,11 +11731,51 @@ target_link_libraries(grpc_tls_certificate_distributor_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(grpc_tls_certificate_provider_test + test/core/security/grpc_tls_certificate_provider_test.cc + test/core/security/tls_utils.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(grpc_tls_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(grpc_tls_certificate_provider_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + grpc_test_util + grpc + gpr + address_sorting + upb +) + + endif() if(gRPC_BUILD_TESTS) add_executable(grpc_tls_credentials_options_test test/core/security/grpc_tls_credentials_options_test.cc + test/core/security/tls_utils.cc third_party/googletest/googletest/src/gtest-all.cc third_party/googletest/googlemock/src/gmock-all.cc ) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index cd73d4b42b5..4e94a387acc 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -6208,9 +6208,26 @@ targets: gtest: true build: test language: c++ - headers: [] + headers: + - test/core/security/tls_utils.h src: - test/core/security/grpc_tls_certificate_distributor_test.cc + - test/core/security/tls_utils.cc + deps: + - grpc_test_util + - grpc + - gpr + - address_sorting + - upb +- name: grpc_tls_certificate_provider_test + gtest: true + build: test + language: c++ + headers: + - test/core/security/tls_utils.h + src: + - test/core/security/grpc_tls_certificate_provider_test.cc + - test/core/security/tls_utils.cc deps: - grpc_test_util - grpc @@ -6221,9 +6238,11 @@ targets: gtest: true build: test language: c++ - headers: [] + headers: + - test/core/security/tls_utils.h src: - test/core/security/grpc_tls_credentials_options_test.cc + - test/core/security/tls_utils.cc deps: - grpc_test_util - grpc diff --git a/grpc.def b/grpc.def index aecf99a9bfe..132892312d2 100644 --- a/grpc.def +++ b/grpc.def @@ -139,6 +139,7 @@ EXPORTS grpc_tls_identity_pairs_add_pair grpc_tls_identity_pairs_destroy grpc_tls_certificate_provider_static_data_create + grpc_tls_certificate_provider_file_watcher_create grpc_tls_certificate_provider_release grpc_tls_credentials_options_create grpc_tls_credentials_options_set_cert_request_type diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h index 12f39ec2c69..1482d7c4197 100644 --- a/include/grpc/grpc_security.h +++ b/include/grpc/grpc_security.h @@ -807,6 +807,31 @@ GRPCAPI grpc_tls_certificate_provider* grpc_tls_certificate_provider_static_data_create( const char* root_certificate, grpc_tls_identity_pairs* pem_key_cert_pairs); +/** + * Creates a grpc_tls_certificate_provider that will watch the credential + * changes on the file system. This provider will always return the up-to-date + * cert data for all the cert names callers set through + * |grpc_tls_credentials_options|. Note that this API only supports one key-cert + * file and hence one set of identity key-cert pair, so SNI(Server Name + * Indication) is not supported. + * - private_key_path is the file path of the private key. This must be set if + * |identity_certificate_path| is set. Otherwise, it could be null if no + * identity credentials are needed. + * - identity_certificate_path is the file path of the identity certificate + * chain. This must be set if |private_key_path| is set. Otherwise, it could + * be null if no identity credentials are needed. + * - root_cert_path is the file path to the root certificate bundle. This + * may be null if no root certs are needed. + * - refresh_interval_sec is the refreshing interval that we will check the + * files for updates. + * It does not take ownership of parameters. + * It is used for experimental purpose for now and subject to change. + */ +GRPCAPI grpc_tls_certificate_provider* +grpc_tls_certificate_provider_file_watcher_create( + const char* private_key_path, const char* identity_certificate_path, + const char* root_cert_path, unsigned int refresh_interval_sec); + /** * Releases a grpc_tls_certificate_provider object. The creator of the * grpc_tls_certificate_provider object is responsible for its release. It is diff --git a/include/grpcpp/security/tls_certificate_provider.h b/include/grpcpp/security/tls_certificate_provider.h index 797687c66c2..ec006f353b1 100644 --- a/include/grpcpp/security/tls_certificate_provider.h +++ b/include/grpcpp/security/tls_certificate_provider.h @@ -66,7 +66,57 @@ class StaticDataCertificateProvider : public CertificateProviderInterface { const std::vector& identity_key_cert_pairs) : StaticDataCertificateProvider("", identity_key_cert_pairs) {} - ~StaticDataCertificateProvider(); + ~StaticDataCertificateProvider() override; + + grpc_tls_certificate_provider* c_provider() override { return c_provider_; } + + private: + grpc_tls_certificate_provider* c_provider_ = nullptr; +}; + +// A CertificateProviderInterface implementation that will watch the credential +// changes on the file system. This provider will always return the up-to-date +// cert data for all the cert names callers set through |TlsCredentialsOptions|. +// Several things to note: +// 1. This API only supports one key-cert file and hence one set of identity +// key-cert pair, so SNI(Server Name Indication) is not supported. +// 2. The private key and identity certificate should always match. This API +// guarantees atomic read, and it is the callers' responsibility to do atomic +// updates. There are many ways to atomically update the key and certs in the +// file system. To name a few: +// 1) creating a new directory, renaming the old directory to a new name, and +// then renaming the new directory to the original name of the old directory. +// 2) using a symlink for the directory. When need to change, put new +// credential data in a new directory, and change symlink. +class FileWatcherCertificateProvider final + : public CertificateProviderInterface { + public: + // Constructor to get credential updates from root and identity file paths. + // + // @param private_key_path is the file path of the private key. + // @param identity_certificate_path is the file path of the identity + // certificate chain. + // @param root_cert_path is the file path to the root certificate bundle. + // @param refresh_interval_sec is the refreshing interval that we will check + // the files for updates. + FileWatcherCertificateProvider(const std::string& private_key_path, + const std::string& identity_certificate_path, + const std::string& root_cert_path, + unsigned int refresh_interval_sec); + // Constructor to get credential updates from identity file paths only. + FileWatcherCertificateProvider(const std::string& private_key_path, + const std::string& identity_certificate_path, + unsigned int refresh_interval_sec) + : FileWatcherCertificateProvider(private_key_path, + identity_certificate_path, "", + refresh_interval_sec) {} + // Constructor to get credential updates from root file path only. + FileWatcherCertificateProvider(const std::string& root_cert_path, + unsigned int refresh_interval_sec) + : FileWatcherCertificateProvider("", "", root_cert_path, + refresh_interval_sec) {} + + ~FileWatcherCertificateProvider() override; grpc_tls_certificate_provider* c_provider() override { return c_provider_; } diff --git a/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h b/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h index 35451e77d5c..9ce94433c29 100644 --- a/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h +++ b/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h @@ -72,12 +72,7 @@ struct grpc_tls_certificate_distributor grpc_error* identity_cert_error) = 0; }; - // Sets the key materials based on their certificate name. Note that we are - // not doing any copies for pem_root_certs and pem_key_cert_pairs. For - // pem_root_certs, the original string contents need to outlive the - // distributor; for pem_key_cert_pairs, internally it is taking two - // unique_ptr(s) to the credential string, so the ownership is actually - // transferred. + // Sets the key materials based on their certificate name. // // @param cert_name The name of the certificates being updated. // @param pem_root_certs The content of root certificates. diff --git a/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.cc b/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.cc index 80ea4eaee3b..9e69d986887 100644 --- a/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.cc +++ b/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.cc @@ -22,6 +22,8 @@ #include #include +#include "src/core/lib/gprpp/stat.h" +#include "src/core/lib/slice/slice_internal.h" #include "src/core/lib/surface/api_trace.h" namespace grpc_core { @@ -35,20 +37,334 @@ StaticDataCertificateProvider::StaticDataCertificateProvider( distributor_->SetWatchStatusCallback([this](std::string cert_name, bool root_being_watched, bool identity_being_watched) { - if (!root_being_watched && !identity_being_watched) return; + grpc_core::MutexLock lock(&mu_); absl::optional root_certificate; absl::optional pem_key_cert_pairs; - if (root_being_watched) { + StaticDataCertificateProvider::WatcherInfo& info = watcher_info_[cert_name]; + if (!info.root_being_watched && root_being_watched && + !root_certificate_.empty()) { root_certificate = root_certificate_; } - if (identity_being_watched) { + info.root_being_watched = root_being_watched; + if (!info.identity_being_watched && identity_being_watched && + !pem_key_cert_pairs_.empty()) { pem_key_cert_pairs = pem_key_cert_pairs_; } - distributor_->SetKeyMaterials(cert_name, std::move(root_certificate), - std::move(pem_key_cert_pairs)); + info.identity_being_watched = identity_being_watched; + if (!info.root_being_watched && !info.identity_being_watched) { + watcher_info_.erase(cert_name); + } + const bool root_has_update = root_certificate.has_value(); + const bool identity_has_update = pem_key_cert_pairs.has_value(); + if (root_has_update || identity_has_update) { + distributor_->SetKeyMaterials(cert_name, std::move(root_certificate), + std::move(pem_key_cert_pairs)); + } + grpc_error* root_cert_error = GRPC_ERROR_NONE; + grpc_error* identity_cert_error = GRPC_ERROR_NONE; + if (root_being_watched && !root_has_update) { + root_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "Unable to get latest root certificates."); + } + if (identity_being_watched && !identity_has_update) { + identity_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "Unable to get latest identity certificates."); + } + if (root_cert_error != GRPC_ERROR_NONE || + identity_cert_error != GRPC_ERROR_NONE) { + distributor_->SetErrorForCert(cert_name, root_cert_error, + identity_cert_error); + } + }); +} + +StaticDataCertificateProvider::~StaticDataCertificateProvider() { + // Reset distributor's callback to make sure the callback won't be invoked + // again after this object(provider) is destroyed. + distributor_->SetWatchStatusCallback(nullptr); +} + +namespace { + +gpr_timespec TimeoutSecondsToDeadline(int64_t seconds) { + return gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(seconds, GPR_TIMESPAN)); +} + +} // namespace + +FileWatcherCertificateProvider::FileWatcherCertificateProvider( + std::string private_key_path, std::string identity_certificate_path, + std::string root_cert_path, unsigned int refresh_interval_sec) + : private_key_path_(std::move(private_key_path)), + identity_certificate_path_(std::move(identity_certificate_path)), + root_cert_path_(std::move(root_cert_path)), + refresh_interval_sec_(refresh_interval_sec), + distributor_(MakeRefCounted()) { + // Private key and identity cert files must be both set or both unset. + GPR_ASSERT(private_key_path_.empty() == identity_certificate_path_.empty()); + // Must be watching either root or identity certs. + GPR_ASSERT(!private_key_path_.empty() || !root_cert_path_.empty()); + gpr_event_init(&shutdown_event_); + ForceUpdate(); + auto thread_lambda = [](void* arg) { + FileWatcherCertificateProvider* provider = + static_cast(arg); + GPR_ASSERT(provider != nullptr); + while (true) { + void* value = gpr_event_wait( + &provider->shutdown_event_, + TimeoutSecondsToDeadline(provider->refresh_interval_sec_)); + if (value != nullptr) { + return; + }; + provider->ForceUpdate(); + } + }; + refresh_thread_ = grpc_core::Thread( + "FileWatcherCertificateProvider_refreshing_thread", thread_lambda, this); + refresh_thread_.Start(); + distributor_->SetWatchStatusCallback([this](std::string cert_name, + bool root_being_watched, + bool identity_being_watched) { + grpc_core::MutexLock lock(&mu_); + absl::optional root_certificate; + absl::optional pem_key_cert_pairs; + FileWatcherCertificateProvider::WatcherInfo& info = + watcher_info_[cert_name]; + if (!info.root_being_watched && root_being_watched && + !root_certificate_.empty()) { + root_certificate = root_certificate_; + } + info.root_being_watched = root_being_watched; + if (!info.identity_being_watched && identity_being_watched && + !pem_key_cert_pairs_.empty()) { + pem_key_cert_pairs = pem_key_cert_pairs_; + } + info.identity_being_watched = identity_being_watched; + if (!info.root_being_watched && !info.identity_being_watched) { + watcher_info_.erase(cert_name); + } + ExecCtx exec_ctx; + if (root_certificate.has_value() || pem_key_cert_pairs.has_value()) { + distributor_->SetKeyMaterials(cert_name, root_certificate, + pem_key_cert_pairs); + } + grpc_error* root_cert_error = GRPC_ERROR_NONE; + grpc_error* identity_cert_error = GRPC_ERROR_NONE; + if (root_being_watched && !root_certificate.has_value()) { + root_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "Unable to get latest root certificates."); + } + if (identity_being_watched && !pem_key_cert_pairs.has_value()) { + identity_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "Unable to get latest identity certificates."); + } + if (root_cert_error != GRPC_ERROR_NONE || + identity_cert_error != GRPC_ERROR_NONE) { + distributor_->SetErrorForCert(cert_name, root_cert_error, + identity_cert_error); + } }); } +FileWatcherCertificateProvider::~FileWatcherCertificateProvider() { + // Reset distributor's callback to make sure the callback won't be invoked + // again after this object(provider) is destroyed. + distributor_->SetWatchStatusCallback(nullptr); + gpr_event_set(&shutdown_event_, (void*)(1)); + refresh_thread_.Join(); +} + +void FileWatcherCertificateProvider::ForceUpdate() { + absl::optional root_certificate; + absl::optional pem_key_cert_pairs; + if (!root_cert_path_.empty()) { + root_certificate = ReadRootCertificatesFromFile(root_cert_path_); + } + if (!private_key_path_.empty()) { + pem_key_cert_pairs = ReadIdentityKeyCertPairFromFiles( + private_key_path_, identity_certificate_path_); + } + grpc_core::MutexLock lock(&mu_); + const bool root_cert_changed = + (!root_certificate.has_value() && !root_certificate_.empty()) || + (root_certificate.has_value() && root_certificate_ != *root_certificate); + if (root_cert_changed) { + if (root_certificate.has_value()) { + root_certificate_ = std::move(*root_certificate); + } else { + root_certificate_ = ""; + } + } + const bool identity_cert_changed = + (!pem_key_cert_pairs.has_value() && !pem_key_cert_pairs_.empty()) || + (pem_key_cert_pairs.has_value() && + pem_key_cert_pairs_ != *pem_key_cert_pairs); + if (identity_cert_changed) { + if (pem_key_cert_pairs.has_value()) { + pem_key_cert_pairs_ = std::move(*pem_key_cert_pairs); + } else { + pem_key_cert_pairs_ = {}; + } + } + if (root_cert_changed || identity_cert_changed) { + ExecCtx exec_ctx; + grpc_error* root_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "Unable to get latest root certificates."); + grpc_error* identity_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "Unable to get latest identity certificates."); + for (const auto& p : watcher_info_) { + const std::string& cert_name = p.first; + const WatcherInfo& info = p.second; + absl::optional root_to_report; + absl::optional identity_to_report; + // Set key materials to the distributor if their contents changed. + if (info.root_being_watched && !root_certificate_.empty() && + root_cert_changed) { + root_to_report = root_certificate_; + } + if (info.identity_being_watched && !pem_key_cert_pairs_.empty() && + identity_cert_changed) { + identity_to_report = pem_key_cert_pairs_; + } + if (root_to_report.has_value() || identity_to_report.has_value()) { + distributor_->SetKeyMaterials(cert_name, std::move(root_to_report), + std::move(identity_to_report)); + } + // Report errors to the distributor if the contents are empty. + const bool report_root_error = + info.root_being_watched && root_certificate_.empty(); + const bool report_identity_error = + info.identity_being_watched && pem_key_cert_pairs_.empty(); + if (report_root_error || report_identity_error) { + distributor_->SetErrorForCert( + cert_name, + report_root_error ? GRPC_ERROR_REF(root_cert_error) + : GRPC_ERROR_NONE, + report_identity_error ? GRPC_ERROR_REF(identity_cert_error) + : GRPC_ERROR_NONE); + } + } + GRPC_ERROR_UNREF(root_cert_error); + GRPC_ERROR_UNREF(identity_cert_error); + } +} + +absl::optional +FileWatcherCertificateProvider::ReadRootCertificatesFromFile( + const std::string& root_cert_full_path) { + // Read the root file. + grpc_slice root_slice = grpc_empty_slice(); + grpc_error* root_error = + grpc_load_file(root_cert_full_path.c_str(), 0, &root_slice); + if (root_error != GRPC_ERROR_NONE) { + gpr_log(GPR_ERROR, "Reading file %s failed: %s", + root_cert_full_path.c_str(), grpc_error_string(root_error)); + GRPC_ERROR_UNREF(root_error); + return absl::nullopt; + } + std::string root_cert(StringViewFromSlice(root_slice)); + grpc_slice_unref_internal(root_slice); + return root_cert; +} + +namespace { + +// This helper function gets the last-modified time of |filename|. When failed, +// it logs the error and returns 0. +time_t GetModificationTime(const char* filename) { + time_t ts = 0; + absl::Status status = grpc_core::GetFileModificationTime(filename, &ts); + return ts; +} + +} // namespace + +absl::optional +FileWatcherCertificateProvider::ReadIdentityKeyCertPairFromFiles( + const std::string& private_key_path, + const std::string& identity_certificate_path) { + struct SliceWrapper { + grpc_slice slice = grpc_empty_slice(); + ~SliceWrapper() { grpc_slice_unref_internal(slice); } + }; + const int kNumRetryAttempts = 3; + for (int i = 0; i < kNumRetryAttempts; ++i) { + // TODO(ZhenLian): replace the timestamp approach with key-match approach + // once the latter is implemented. + // Checking the last modification of identity files before reading. + time_t identity_key_ts_before = + GetModificationTime(private_key_path.c_str()); + if (identity_key_ts_before == 0) { + gpr_log( + GPR_ERROR, + "Failed to get the file's modification time of %s. Start retrying...", + private_key_path.c_str()); + continue; + } + time_t identity_cert_ts_before = + GetModificationTime(identity_certificate_path.c_str()); + if (identity_cert_ts_before == 0) { + gpr_log( + GPR_ERROR, + "Failed to get the file's modification time of %s. Start retrying...", + identity_certificate_path.c_str()); + continue; + } + // Read the identity files. + SliceWrapper key_slice, cert_slice; + grpc_error* key_error = + grpc_load_file(private_key_path.c_str(), 0, &key_slice.slice); + if (key_error != GRPC_ERROR_NONE) { + gpr_log(GPR_ERROR, "Reading file %s failed: %s. Start retrying...", + private_key_path.c_str(), grpc_error_string(key_error)); + GRPC_ERROR_UNREF(key_error); + continue; + } + grpc_error* cert_error = + grpc_load_file(identity_certificate_path.c_str(), 0, &cert_slice.slice); + if (cert_error != GRPC_ERROR_NONE) { + gpr_log(GPR_ERROR, "Reading file %s failed: %s. Start retrying...", + identity_certificate_path.c_str(), grpc_error_string(cert_error)); + GRPC_ERROR_UNREF(cert_error); + continue; + } + std::string private_key(StringViewFromSlice(key_slice.slice)); + std::string cert_chain(StringViewFromSlice(cert_slice.slice)); + 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.c_str()); + ssl_pair->cert_chain = gpr_strdup(cert_chain.c_str()); + PemKeyCertPairList identity_pairs; + identity_pairs.emplace_back(ssl_pair); + // Checking the last modification of identity files before reading. + time_t identity_key_ts_after = + GetModificationTime(private_key_path.c_str()); + if (identity_key_ts_before != identity_key_ts_after) { + gpr_log(GPR_ERROR, + "Last modified time before and after reading %s is not the same. " + "Start retrying...", + private_key_path.c_str()); + continue; + } + time_t identity_cert_ts_after = + GetModificationTime(identity_certificate_path.c_str()); + if (identity_cert_ts_before != identity_cert_ts_after) { + gpr_log(GPR_ERROR, + "Last modified time before and after reading %s is not the same. " + "Start retrying...", + identity_certificate_path.c_str()); + continue; + } + return identity_pairs; + } + gpr_log(GPR_ERROR, + "All retry attempts failed. Will try again after the next interval."); + return absl::nullopt; +} + } // namespace grpc_core /** -- Wrapper APIs declared in grpc_security.h -- **/ @@ -69,6 +385,16 @@ grpc_tls_certificate_provider* grpc_tls_certificate_provider_static_data_create( std::move(root_cert_core), std::move(identity_pairs_core)); } +grpc_tls_certificate_provider* +grpc_tls_certificate_provider_file_watcher_create( + const char* private_key_path, const char* identity_certificate_path, + const char* root_cert_path, unsigned int refresh_interval_sec) { + return new grpc_core::FileWatcherCertificateProvider( + private_key_path == nullptr ? "" : private_key_path, + identity_certificate_path == nullptr ? "" : identity_certificate_path, + root_cert_path == nullptr ? "" : root_cert_path, refresh_interval_sec); +} + void grpc_tls_certificate_provider_release( grpc_tls_certificate_provider* provider) { GRPC_API_TRACE("grpc_tls_certificate_provider_release(provider=%p)", 1, diff --git a/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.h b/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.h index dae6fd21586..20fbb166f8f 100644 --- a/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.h +++ b/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.h @@ -26,6 +26,8 @@ #include "src/core/lib/gprpp/ref_counted.h" #include "src/core/lib/gprpp/ref_counted_ptr.h" +#include "src/core/lib/gprpp/thd.h" +#include "src/core/lib/iomgr/load_file.h" #include "src/core/lib/iomgr/pollset_set.h" #include "src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h" #include "src/core/lib/security/security_connector/ssl_utils.h" @@ -59,14 +61,76 @@ class StaticDataCertificateProvider final std::string root_certificate, grpc_core::PemKeyCertPairList pem_key_cert_pairs); + ~StaticDataCertificateProvider() override; + + RefCountedPtr distributor() const override { + return distributor_; + } + + private: + struct WatcherInfo { + bool root_being_watched = false; + bool identity_being_watched = false; + }; + RefCountedPtr distributor_; + std::string root_certificate_; + grpc_core::PemKeyCertPairList pem_key_cert_pairs_; + // Guards members below. + grpc_core::Mutex mu_; + // Stores each cert_name we get from the distributor callback and its watcher + // information. + std::map watcher_info_; +}; + +// A provider class that will watch the credential changes on the file system. +class FileWatcherCertificateProvider final + : public grpc_tls_certificate_provider { + public: + FileWatcherCertificateProvider(std::string private_key_path, + std::string identity_certificate_path, + std::string root_cert_path, + unsigned int refresh_interval_sec); + + ~FileWatcherCertificateProvider() override; + RefCountedPtr distributor() const override { return distributor_; } private: + struct WatcherInfo { + bool root_being_watched = false; + bool identity_being_watched = false; + }; + // Force an update from the file system regardless of the interval. + void ForceUpdate(); + // Read the root certificates from files and update the distributor. + absl::optional ReadRootCertificatesFromFile( + const std::string& root_cert_full_path); + // Read the root certificates from files and update the distributor. + absl::optional ReadIdentityKeyCertPairFromFiles( + const std::string& private_key_file_name, + const std::string& identity_certificate_file_name); + + // Information that is used by the refreshing thread. + std::string private_key_path_; + std::string identity_certificate_path_; + std::string root_cert_path_; + unsigned int refresh_interval_sec_ = 0; + RefCountedPtr distributor_; + grpc_core::Thread refresh_thread_; + gpr_event shutdown_event_; + + // Guards members below. + grpc_core::Mutex mu_; + // The most-recent credential data. It will be empty if the most recent read + // attempt failed. std::string root_certificate_; grpc_core::PemKeyCertPairList pem_key_cert_pairs_; + // Stores each cert_name we get from the distributor callback and its watcher + // information. + std::map watcher_info_; }; } // namespace grpc_core diff --git a/src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h b/src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h index c67238423e3..2aae29b4b68 100644 --- a/src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h +++ b/src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h @@ -147,7 +147,6 @@ struct grpc_tls_credentials_options server_authorization_check_config_ = std::move(config); } // Sets the provider in the options. - // This should only be used by C-core API for Tls*Creds case. void set_certificate_provider( grpc_core::RefCountedPtr provider) { provider_ = std::move(provider); diff --git a/src/core/lib/security/security_connector/tls/tls_security_connector.h b/src/core/lib/security/security_connector/tls/tls_security_connector.h index d9159598819..453c531925d 100644 --- a/src/core/lib/security/security_connector/tls/tls_security_connector.h +++ b/src/core/lib/security/security_connector/tls/tls_security_connector.h @@ -74,13 +74,12 @@ class TlsChannelSecurityConnector final return client_handshaker_factory_; }; - const absl::optional& RootCertsForTesting() { + absl::optional RootCertsForTesting() { grpc_core::MutexLock lock(&mu_); return pem_root_certs_; } - const absl::optional& - KeyCertPairListForTesting() { + absl::optional KeyCertPairListForTesting() { grpc_core::MutexLock lock(&mu_); return pem_key_cert_pair_list_; } diff --git a/src/cpp/common/tls_certificate_provider.cc b/src/cpp/common/tls_certificate_provider.cc index 3550e28608e..2deea5f7762 100644 --- a/src/cpp/common/tls_certificate_provider.cc +++ b/src/cpp/common/tls_certificate_provider.cc @@ -41,5 +41,19 @@ StaticDataCertificateProvider::~StaticDataCertificateProvider() { grpc_tls_certificate_provider_release(c_provider_); }; +FileWatcherCertificateProvider::FileWatcherCertificateProvider( + const std::string& private_key_path, + const std::string& identity_certificate_path, + const std::string& root_cert_path, unsigned int refresh_interval_sec) { + c_provider_ = grpc_tls_certificate_provider_file_watcher_create( + private_key_path.c_str(), identity_certificate_path.c_str(), + root_cert_path.c_str(), refresh_interval_sec); + GPR_ASSERT(c_provider_ != nullptr); +}; + +FileWatcherCertificateProvider::~FileWatcherCertificateProvider() { + grpc_tls_certificate_provider_release(c_provider_); +}; + } // namespace experimental } // namespace grpc diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.c b/src/ruby/ext/grpc/rb_grpc_imports.generated.c index 9024a8e581b..90b23aec822 100644 --- a/src/ruby/ext/grpc/rb_grpc_imports.generated.c +++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.c @@ -162,6 +162,7 @@ grpc_tls_identity_pairs_create_type grpc_tls_identity_pairs_create_import; grpc_tls_identity_pairs_add_pair_type grpc_tls_identity_pairs_add_pair_import; grpc_tls_identity_pairs_destroy_type grpc_tls_identity_pairs_destroy_import; grpc_tls_certificate_provider_static_data_create_type grpc_tls_certificate_provider_static_data_create_import; +grpc_tls_certificate_provider_file_watcher_create_type grpc_tls_certificate_provider_file_watcher_create_import; grpc_tls_certificate_provider_release_type grpc_tls_certificate_provider_release_import; grpc_tls_credentials_options_create_type grpc_tls_credentials_options_create_import; grpc_tls_credentials_options_set_cert_request_type_type grpc_tls_credentials_options_set_cert_request_type_import; @@ -440,6 +441,7 @@ void grpc_rb_load_imports(HMODULE library) { grpc_tls_identity_pairs_add_pair_import = (grpc_tls_identity_pairs_add_pair_type) GetProcAddress(library, "grpc_tls_identity_pairs_add_pair"); grpc_tls_identity_pairs_destroy_import = (grpc_tls_identity_pairs_destroy_type) GetProcAddress(library, "grpc_tls_identity_pairs_destroy"); grpc_tls_certificate_provider_static_data_create_import = (grpc_tls_certificate_provider_static_data_create_type) GetProcAddress(library, "grpc_tls_certificate_provider_static_data_create"); + grpc_tls_certificate_provider_file_watcher_create_import = (grpc_tls_certificate_provider_file_watcher_create_type) GetProcAddress(library, "grpc_tls_certificate_provider_file_watcher_create"); grpc_tls_certificate_provider_release_import = (grpc_tls_certificate_provider_release_type) GetProcAddress(library, "grpc_tls_certificate_provider_release"); grpc_tls_credentials_options_create_import = (grpc_tls_credentials_options_create_type) GetProcAddress(library, "grpc_tls_credentials_options_create"); grpc_tls_credentials_options_set_cert_request_type_import = (grpc_tls_credentials_options_set_cert_request_type_type) GetProcAddress(library, "grpc_tls_credentials_options_set_cert_request_type"); diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.h b/src/ruby/ext/grpc/rb_grpc_imports.generated.h index 1079e4253ee..0224b947d11 100644 --- a/src/ruby/ext/grpc/rb_grpc_imports.generated.h +++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.h @@ -461,6 +461,9 @@ extern grpc_tls_identity_pairs_destroy_type grpc_tls_identity_pairs_destroy_impo typedef grpc_tls_certificate_provider*(*grpc_tls_certificate_provider_static_data_create_type)(const char* root_certificate, grpc_tls_identity_pairs* pem_key_cert_pairs); extern grpc_tls_certificate_provider_static_data_create_type grpc_tls_certificate_provider_static_data_create_import; #define grpc_tls_certificate_provider_static_data_create grpc_tls_certificate_provider_static_data_create_import +typedef grpc_tls_certificate_provider*(*grpc_tls_certificate_provider_file_watcher_create_type)(const char* private_key_path, const char* identity_certificate_path, const char* root_cert_path, unsigned int refresh_interval_sec); +extern grpc_tls_certificate_provider_file_watcher_create_type grpc_tls_certificate_provider_file_watcher_create_import; +#define grpc_tls_certificate_provider_file_watcher_create grpc_tls_certificate_provider_file_watcher_create_import typedef void(*grpc_tls_certificate_provider_release_type)(grpc_tls_certificate_provider* provider); extern grpc_tls_certificate_provider_release_type grpc_tls_certificate_provider_release_import; #define grpc_tls_certificate_provider_release grpc_tls_certificate_provider_release_import diff --git a/test/core/end2end/fixtures/h2_tls.cc b/test/core/end2end/fixtures/h2_tls.cc index 4689cee5c91..9157e1eee8f 100644 --- a/test/core/end2end/fixtures/h2_tls.cc +++ b/test/core/end2end/fixtures/h2_tls.cc @@ -39,6 +39,7 @@ #include "test/core/util/port.h" #include "test/core/util/test_config.h" +// For normal TLS connections. #define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem" #define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem" #define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key" @@ -60,7 +61,7 @@ struct fullstack_secure_fixture_data { grpc_tls_certificate_provider* server_provider = nullptr; }; -static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack( +static grpc_end2end_test_fixture chttp2_create_fixture_static_data( grpc_channel_args* /*client_args*/, grpc_channel_args* /*server_args*/, grpc_tls_version tls_version) { grpc_end2end_test_fixture f; @@ -101,16 +102,47 @@ static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack( return f; } -static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_2( +static grpc_end2end_test_fixture chttp2_create_fixture_cert_watcher( + grpc_channel_args* /*client_args*/, grpc_channel_args* /*server_args*/, + grpc_tls_version tls_version) { + grpc_end2end_test_fixture f; + int port = grpc_pick_unused_port_or_die(); + fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data(); + memset(&f, 0, sizeof(f)); + ffd->localaddr = grpc_core::JoinHostPort("localhost", port); + ffd->tls_version = tls_version; + ffd->client_provider = grpc_tls_certificate_provider_file_watcher_create( + SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1); + ffd->server_provider = grpc_tls_certificate_provider_file_watcher_create( + SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1); + f.fixture_data = ffd; + f.cq = grpc_completion_queue_create_for_next(nullptr); + f.shutdown_cq = grpc_completion_queue_create_for_pluck(nullptr); + return f; +} + +static grpc_end2end_test_fixture chttp2_create_fixture_static_data_tls1_2( grpc_channel_args* client_args, grpc_channel_args* server_args) { - return chttp2_create_fixture_secure_fullstack(client_args, server_args, - grpc_tls_version::TLS1_2); + return chttp2_create_fixture_static_data(client_args, server_args, + grpc_tls_version::TLS1_2); } -static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_3( +static grpc_end2end_test_fixture chttp2_create_fixture_static_data_tls1_3( grpc_channel_args* client_args, grpc_channel_args* server_args) { - return chttp2_create_fixture_secure_fullstack(client_args, server_args, - grpc_tls_version::TLS1_3); + return chttp2_create_fixture_static_data(client_args, server_args, + grpc_tls_version::TLS1_3); +} + +static grpc_end2end_test_fixture chttp2_create_fixture_cert_watcher_tls1_2( + grpc_channel_args* client_args, grpc_channel_args* server_args) { + return chttp2_create_fixture_cert_watcher(client_args, server_args, + grpc_tls_version::TLS1_2); +} + +static grpc_end2end_test_fixture chttp2_create_fixture_cert_watcher_tls1_3( + grpc_channel_args* client_args, grpc_channel_args* server_args) { + return chttp2_create_fixture_cert_watcher(client_args, server_args, + grpc_tls_version::TLS1_3); } static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/, @@ -262,21 +294,47 @@ static void chttp2_init_server(grpc_end2end_test_fixture* f, } static grpc_end2end_test_config configs[] = { - /* client sync reload async authz + server sync reload. */ + // client: static data provider + async custom verification + // server: static data provider + // extra: TLS 1.2 {"chttp2/simple_ssl_fullstack_tls1_2", FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, - "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_2, + "foo.test.google.fr", chttp2_create_fixture_static_data_tls1_2, chttp2_init_client, chttp2_init_server, chttp2_tear_down_secure_fullstack}, + // client: static data provider + async custom verification + // server: static data provider + // extra: TLS 1.3 {"chttp2/simple_ssl_fullstack_tls1_3", FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, - "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_3, + "foo.test.google.fr", chttp2_create_fixture_static_data_tls1_3, chttp2_init_client, chttp2_init_server, chttp2_tear_down_secure_fullstack}, + // client: certificate watcher provider + async custom verification + // server: certificate watcher provider + // extra: TLS 1.2 + {"chttp2/reloading_from_files_ssl_fullstack_tls1_2", + FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | + FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | + FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | + FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, + "foo.test.google.fr", chttp2_create_fixture_cert_watcher_tls1_2, + chttp2_init_client, chttp2_init_server, chttp2_tear_down_secure_fullstack}, + // client: certificate watcher provider + async custom verification + // server: certificate watcher provider + // extra: TLS 1.3 + {"chttp2/reloading_from_files_ssl_fullstack_tls1_3", + FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | + FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | + FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | + FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, + "foo.test.google.fr", chttp2_create_fixture_cert_watcher_tls1_3, + chttp2_init_client, chttp2_init_server, chttp2_tear_down_secure_fullstack}, + }; int main(int argc, char** argv) { diff --git a/test/core/security/BUILD b/test/core/security/BUILD index e5760284830..31b77a22185 100644 --- a/test/core/security/BUILD +++ b/test/core/security/BUILD @@ -287,6 +287,15 @@ grpc_cc_test( ], ) +grpc_cc_library( + name = "tls_utils", + srcs = ["tls_utils.cc"], + hdrs = ["tls_utils.h"], + language = "C++", + visibility = ["//test/cpp:__subpackages__"], + deps = ["//:grpc"], +) + grpc_cc_test( name = "tls_security_connector_test", srcs = ["tls_security_connector_test.cc"], @@ -318,12 +327,17 @@ grpc_cc_test( srcs = ["grpc_tls_credentials_options_test.cc"], data = [ "//src/core/tsi/test_creds:ca.pem", + "//src/core/tsi/test_creds:multi-domain.key", + "//src/core/tsi/test_creds:multi-domain.pem", + "//src/core/tsi/test_creds:server0.key", + "//src/core/tsi/test_creds:server0.pem", "//src/core/tsi/test_creds:server1.key", "//src/core/tsi/test_creds:server1.pem", ], external_deps = ["gtest"], language = "C++", deps = [ + ":tls_utils", "//:gpr", "//:grpc", "//:grpc_secure", @@ -337,6 +351,30 @@ grpc_cc_test( external_deps = ["gtest"], language = "C++", deps = [ + ":tls_utils", + "//:gpr", + "//:grpc", + "//:grpc_secure", + "//test/core/util:grpc_test_util", + ], +) + +grpc_cc_test( + name = "grpc_tls_certificate_provider_test", + srcs = ["grpc_tls_certificate_provider_test.cc"], + data = [ + "//src/core/tsi/test_creds:ca.pem", + "//src/core/tsi/test_creds:multi-domain.key", + "//src/core/tsi/test_creds:multi-domain.pem", + "//src/core/tsi/test_creds:server0.key", + "//src/core/tsi/test_creds:server0.pem", + "//src/core/tsi/test_creds:server1.key", + "//src/core/tsi/test_creds:server1.pem", + ], + external_deps = ["gtest"], + language = "C++", + deps = [ + ":tls_utils", "//:gpr", "//:grpc", "//:grpc_secure", diff --git a/test/core/security/grpc_tls_certificate_distributor_test.cc b/test/core/security/grpc_tls_certificate_distributor_test.cc index 61be71c8ac0..b8dfaa19576 100644 --- a/test/core/security/grpc_tls_certificate_distributor_test.cc +++ b/test/core/security/grpc_tls_certificate_distributor_test.cc @@ -28,8 +28,11 @@ #include #include "src/core/lib/slice/slice_internal.h" +#include "test/core/security/tls_utils.h" #include "test/core/util/test_config.h" +namespace grpc_core { + namespace testing { constexpr const char* kCertName1 = "cert_1_name"; @@ -53,29 +56,14 @@ class GrpcTlsCertificateDistributorTest : public ::testing::Test { // Forward declaration. class TlsCertificatesTestWatcher; - static grpc_core::PemKeyCertPairList MakeCertKeyPairs(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); - grpc_core::PemKeyCertPairList pem_key_cert_pairs; - pem_key_cert_pairs.emplace_back(ssl_pair); - return pem_key_cert_pairs; - } - // CredentialInfo contains the parameters when calling OnCertificatesChanged // of a watcher. When OnCertificatesChanged is invoked, we will push a // CredentialInfo to the cert_update_queue of state_, and check in each test // if the status updates are correct. struct CredentialInfo { std::string root_certs; - grpc_core::PemKeyCertPairList key_cert_pairs; - CredentialInfo(std::string root, grpc_core::PemKeyCertPairList key_cert) + PemKeyCertPairList key_cert_pairs; + CredentialInfo(std::string root, PemKeyCertPairList key_cert) : root_certs(std::move(root)), key_cert_pairs(std::move(key_cert)) {} bool operator==(const CredentialInfo& other) const { return root_certs == other.root_certs && @@ -128,12 +116,12 @@ class GrpcTlsCertificateDistributorTest : public ::testing::Test { void OnCertificatesChanged( absl::optional root_certs, - absl::optional key_cert_pairs) override { + absl::optional key_cert_pairs) override { std::string updated_root; if (root_certs.has_value()) { updated_root = std::string(*root_certs); } - grpc_core::PemKeyCertPairList updated_identity; + PemKeyCertPairList updated_identity; if (key_cert_pairs.has_value()) { updated_identity = std::move(*key_cert_pairs); } @@ -151,8 +139,7 @@ class GrpcTlsCertificateDistributorTest : public ::testing::Test { grpc_slice root_error_slice; GPR_ASSERT(grpc_error_get_str( root_cert_error, GRPC_ERROR_STR_DESCRIPTION, &root_error_slice)); - root_error_str = - std::string(grpc_core::StringViewFromSlice(root_error_slice)); + root_error_str = std::string(StringViewFromSlice(root_error_slice)); } if (identity_cert_error != GRPC_ERROR_NONE) { grpc_slice identity_error_slice; @@ -160,7 +147,7 @@ class GrpcTlsCertificateDistributorTest : public ::testing::Test { GRPC_ERROR_STR_DESCRIPTION, &identity_error_slice)); identity_error_str = - std::string(grpc_core::StringViewFromSlice(identity_error_slice)); + std::string(StringViewFromSlice(identity_error_slice)); } state_->error_queue.emplace_back(std::move(root_error_str), std::move(identity_error_str)); @@ -202,7 +189,7 @@ class GrpcTlsCertificateDistributorTest : public ::testing::Test { WatcherState* MakeWatcher(absl::optional root_cert_name, absl::optional identity_cert_name) { - grpc_core::MutexLock lock(&mu_); + MutexLock lock(&mu_); watchers_.emplace_back(); // TlsCertificatesTestWatcher ctor takes a pointer to the WatcherState. // It sets WatcherState::watcher to point to itself. @@ -217,7 +204,7 @@ class GrpcTlsCertificateDistributorTest : public ::testing::Test { } void CancelWatch(WatcherState* state) { - grpc_core::MutexLock lock(&mu_); + MutexLock lock(&mu_); distributor_.CancelTlsCertificatesWatch(state->watcher); EXPECT_EQ(state->watcher, nullptr); } @@ -234,7 +221,7 @@ class GrpcTlsCertificateDistributorTest : public ::testing::Test { std::list watchers_; std::deque callback_queue_; // This is to make watchers_ and callback_queue_ thread-safe. - grpc_core::Mutex mu_; + Mutex mu_; }; TEST_F(GrpcTlsCertificateDistributorTest, BasicCredentialBehaviors) { @@ -257,21 +244,21 @@ TEST_F(GrpcTlsCertificateDistributorTest, BasicCredentialBehaviors) { TEST_F(GrpcTlsCertificateDistributorTest, UpdateCredentialsOnAnySide) { WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, true, true))); + ::testing::ElementsAre(CallbackStatus(kCertName1, true, true))); // SetKeyMaterials should trigger watcher's OnCertificatesChanged method. distributor_.SetKeyMaterials( kCertName1, kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)); EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); // Set root certs should trigger watcher's OnCertificatesChanged again. distributor_.SetKeyMaterials(kCertName1, kRootCert2Contents, absl::nullopt); EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert2Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); // Set identity certs should trigger watcher's OnCertificatesChanged again. @@ -280,7 +267,7 @@ TEST_F(GrpcTlsCertificateDistributorTest, UpdateCredentialsOnAnySide) { MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents)); EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert2Contents, MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents)))); CancelWatch(watcher_state_1); @@ -292,12 +279,12 @@ TEST_F(GrpcTlsCertificateDistributorTest, SameIdentityNameDiffRootName) { MakeWatcher(kRootCert1Name, kIdentityCert1Name); EXPECT_THAT( GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kRootCert1Name, true, false), - CallbackStatus(kIdentityCert1Name, false, true))); + ::testing::ElementsAre(CallbackStatus(kRootCert1Name, true, false), + CallbackStatus(kIdentityCert1Name, false, true))); // Register watcher 2. WatcherState* watcher_state_2 = MakeWatcher(kRootCert2Name, kIdentityCert1Name); - EXPECT_THAT(GetCallbackQueue(), testing::ElementsAre(CallbackStatus( + EXPECT_THAT(GetCallbackQueue(), ::testing::ElementsAre(CallbackStatus( kRootCert2Name, true, false))); // Push credential updates to kRootCert1Name and check if the status works as // expected. @@ -305,13 +292,13 @@ TEST_F(GrpcTlsCertificateDistributorTest, SameIdentityNameDiffRootName) { absl::nullopt); // Check the updates are delivered to watcher 1. EXPECT_THAT(watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); + ::testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); // Push credential updates to kRootCert2Name. distributor_.SetKeyMaterials(kRootCert2Name, kRootCert2Contents, absl::nullopt); // Check the updates are delivered to watcher 2. EXPECT_THAT(watcher_state_2->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo(kRootCert2Contents, {}))); + ::testing::ElementsAre(CredentialInfo(kRootCert2Contents, {}))); // Push credential updates to kIdentityCert1Name and check if the status works // as expected. distributor_.SetKeyMaterials( @@ -320,24 +307,24 @@ TEST_F(GrpcTlsCertificateDistributorTest, SameIdentityNameDiffRootName) { // Check the updates are delivered to watcher 1 and watcher 2. EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); EXPECT_THAT( watcher_state_2->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert2Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); // Cancel watcher 1. CancelWatch(watcher_state_1); - EXPECT_THAT(GetCallbackQueue(), testing::ElementsAre(CallbackStatus( + EXPECT_THAT(GetCallbackQueue(), ::testing::ElementsAre(CallbackStatus( kRootCert1Name, false, false))); // Cancel watcher 2. CancelWatch(watcher_state_2); EXPECT_THAT( GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kRootCert2Name, false, false), - CallbackStatus(kIdentityCert1Name, false, false))); + ::testing::ElementsAre(CallbackStatus(kRootCert2Name, false, false), + CallbackStatus(kIdentityCert1Name, false, false))); } TEST_F(GrpcTlsCertificateDistributorTest, SameRootNameDiffIdentityName) { @@ -346,12 +333,12 @@ TEST_F(GrpcTlsCertificateDistributorTest, SameRootNameDiffIdentityName) { MakeWatcher(kRootCert1Name, kIdentityCert1Name); EXPECT_THAT( GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kRootCert1Name, true, false), - CallbackStatus(kIdentityCert1Name, false, true))); + ::testing::ElementsAre(CallbackStatus(kRootCert1Name, true, false), + CallbackStatus(kIdentityCert1Name, false, true))); // Register watcher 2. WatcherState* watcher_state_2 = MakeWatcher(kRootCert1Name, kIdentityCert2Name); - EXPECT_THAT(GetCallbackQueue(), testing::ElementsAre(CallbackStatus( + EXPECT_THAT(GetCallbackQueue(), ::testing::ElementsAre(CallbackStatus( kIdentityCert2Name, false, true))); // Push credential updates to kRootCert1Name and check if the status works as // expected. @@ -359,10 +346,10 @@ TEST_F(GrpcTlsCertificateDistributorTest, SameRootNameDiffIdentityName) { absl::nullopt); // Check the updates are delivered to watcher 1. EXPECT_THAT(watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); + ::testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); // Check the updates are delivered to watcher 2. EXPECT_THAT(watcher_state_2->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); + ::testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); // Push credential updates to SetKeyMaterials. distributor_.SetKeyMaterials( kIdentityCert1Name, absl::nullopt, @@ -370,7 +357,7 @@ TEST_F(GrpcTlsCertificateDistributorTest, SameRootNameDiffIdentityName) { // Check the updates are delivered to watcher 1. EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); // Push credential updates to kIdentityCert2Name. @@ -380,19 +367,19 @@ TEST_F(GrpcTlsCertificateDistributorTest, SameRootNameDiffIdentityName) { // Check the updates are delivered to watcher 2. EXPECT_THAT( watcher_state_2->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert1Contents, MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents)))); // Cancel watcher 1. CancelWatch(watcher_state_1); - EXPECT_THAT(GetCallbackQueue(), testing::ElementsAre(CallbackStatus( + EXPECT_THAT(GetCallbackQueue(), ::testing::ElementsAre(CallbackStatus( kIdentityCert1Name, false, false))); // Cancel watcher 2. CancelWatch(watcher_state_2); EXPECT_THAT( GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kRootCert1Name, false, false), - CallbackStatus(kIdentityCert2Name, false, false))); + ::testing::ElementsAre(CallbackStatus(kRootCert1Name, false, false), + CallbackStatus(kIdentityCert2Name, false, false))); } TEST_F(GrpcTlsCertificateDistributorTest, @@ -400,7 +387,7 @@ TEST_F(GrpcTlsCertificateDistributorTest, // Register watcher 1 watching kCertName1 for both root and identity certs. WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, true, true))); + ::testing::ElementsAre(CallbackStatus(kCertName1, true, true))); // Push credential updates to kCertName1 and check if the status works as // expected. distributor_.SetKeyMaterials( @@ -409,13 +396,13 @@ TEST_F(GrpcTlsCertificateDistributorTest, // Check the updates are delivered to watcher 1. EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); // Cancel watcher 1. CancelWatch(watcher_state_1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, false, false))); + ::testing::ElementsAre(CallbackStatus(kCertName1, false, false))); } TEST_F(GrpcTlsCertificateDistributorTest, @@ -423,11 +410,11 @@ TEST_F(GrpcTlsCertificateDistributorTest, // Register watcher 1 watching kCertName1 for root certs. WatcherState* watcher_state_1 = MakeWatcher(kCertName1, absl::nullopt); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, true, false))); + ::testing::ElementsAre(CallbackStatus(kCertName1, true, false))); // Register watcher 2 watching kCertName1 for identity certs. WatcherState* watcher_state_2 = MakeWatcher(absl::nullopt, kCertName1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, true, true))); + ::testing::ElementsAre(CallbackStatus(kCertName1, true, true))); // Push credential updates to kCertName1 and check if the status works as // expected. distributor_.SetKeyMaterials( @@ -435,39 +422,39 @@ TEST_F(GrpcTlsCertificateDistributorTest, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)); // Check the updates are delivered to watcher 1. EXPECT_THAT(watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); + ::testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); // Check the updates are delivered to watcher 2. EXPECT_THAT(watcher_state_2->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( "", MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); // Push root cert updates to kCertName1. distributor_.SetKeyMaterials(kCertName1, kRootCert2Contents, absl::nullopt); // Check the updates are delivered to watcher 1. EXPECT_THAT(watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo(kRootCert2Contents, {}))); + ::testing::ElementsAre(CredentialInfo(kRootCert2Contents, {}))); // Check the updates are not delivered to watcher 2. - EXPECT_THAT(watcher_state_2->GetCredentialQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_2->GetCredentialQueue(), ::testing::ElementsAre()); // Push identity cert updates to kCertName1. distributor_.SetKeyMaterials( kCertName1, absl::nullopt, MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents)); // Check the updates are not delivered to watcher 1. - EXPECT_THAT(watcher_state_1->GetCredentialQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), ::testing::ElementsAre()); // Check the updates are delivered to watcher 2. EXPECT_THAT(watcher_state_2->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( "", MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents)))); watcher_state_2->cert_update_queue.clear(); // Cancel watcher 2. CancelWatch(watcher_state_2); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, true, false))); + ::testing::ElementsAre(CallbackStatus(kCertName1, true, false))); // Cancel watcher 1. CancelWatch(watcher_state_1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, false, false))); + ::testing::ElementsAre(CallbackStatus(kCertName1, false, false))); } TEST_F(GrpcTlsCertificateDistributorTest, @@ -475,11 +462,11 @@ TEST_F(GrpcTlsCertificateDistributorTest, // Register watcher 1 watching kCertName1 for identity certs. WatcherState* watcher_state_1 = MakeWatcher(absl::nullopt, kCertName1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, false, true))); + ::testing::ElementsAre(CallbackStatus(kCertName1, false, true))); // Register watcher 2 watching kCertName1 for root certs. WatcherState* watcher_state_2 = MakeWatcher(kCertName1, absl::nullopt); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, true, true))); + ::testing::ElementsAre(CallbackStatus(kCertName1, true, true))); // Push credential updates to kCertName1 and check if the status works as // expected. distributor_.SetKeyMaterials( @@ -487,38 +474,38 @@ TEST_F(GrpcTlsCertificateDistributorTest, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)); // Check the updates are delivered to watcher 1. EXPECT_THAT(watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( "", MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); // Check the updates are delivered to watcher 2. EXPECT_THAT(watcher_state_2->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); + ::testing::ElementsAre(CredentialInfo(kRootCert1Contents, {}))); // Push root cert updates to kCertName1. distributor_.SetKeyMaterials(kCertName1, kRootCert2Contents, absl::nullopt); // Check the updates are delivered to watcher 2. EXPECT_THAT(watcher_state_2->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo(kRootCert2Contents, {}))); + ::testing::ElementsAre(CredentialInfo(kRootCert2Contents, {}))); // Check the updates are not delivered to watcher 1. - EXPECT_THAT(watcher_state_1->GetCredentialQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), ::testing::ElementsAre()); // Push identity cert updates to kCertName1. distributor_.SetKeyMaterials( kCertName1, absl::nullopt, MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents)); // Check the updates are not delivered to watcher 2. - EXPECT_THAT(watcher_state_2->GetCredentialQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_2->GetCredentialQueue(), ::testing::ElementsAre()); // Check the updates are delivered to watcher 1. EXPECT_THAT(watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( "", MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents)))); // Cancel watcher 2. CancelWatch(watcher_state_2); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, false, true))); + ::testing::ElementsAre(CallbackStatus(kCertName1, false, true))); // Cancel watcher 1. CancelWatch(watcher_state_1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, false, false))); + ::testing::ElementsAre(CallbackStatus(kCertName1, false, false))); } TEST_F(GrpcTlsCertificateDistributorTest, @@ -527,24 +514,24 @@ TEST_F(GrpcTlsCertificateDistributorTest, // certs. WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, true, true))); + ::testing::ElementsAre(CallbackStatus(kCertName1, true, true))); WatcherState* watcher_state_2 = MakeWatcher(kCertName1, kCertName1); - EXPECT_THAT(GetCallbackQueue(), testing::ElementsAre()); + EXPECT_THAT(GetCallbackQueue(), ::testing::ElementsAre()); // Push credential updates to kCertName1. distributor_.SetKeyMaterials( kCertName1, kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)); // Cancel watcher 2. CancelWatch(watcher_state_2); - EXPECT_THAT(GetCallbackQueue(), testing::ElementsAre()); + EXPECT_THAT(GetCallbackQueue(), ::testing::ElementsAre()); // Cancel watcher 1. CancelWatch(watcher_state_1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, false, false))); + ::testing::ElementsAre(CallbackStatus(kCertName1, false, false))); // Register watcher 3 watching kCertName for root and identity certs. WatcherState* watcher_state_3 = MakeWatcher(kCertName1, kCertName1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, true, true))); + ::testing::ElementsAre(CallbackStatus(kCertName1, true, true))); // Push credential updates to kCertName1. distributor_.SetKeyMaterials( kCertName1, kRootCert2Contents, @@ -552,25 +539,25 @@ TEST_F(GrpcTlsCertificateDistributorTest, // Check the updates are delivered to watcher 3. EXPECT_THAT( watcher_state_3->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert2Contents, MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents)))); // Cancel watcher 3. CancelWatch(watcher_state_3); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, false, false))); + ::testing::ElementsAre(CallbackStatus(kCertName1, false, false))); } TEST_F(GrpcTlsCertificateDistributorTest, ResetCallbackToNull) { // Register watcher 1 watching kCertName1 for root and identity certs. WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1); EXPECT_THAT(GetCallbackQueue(), - testing::ElementsAre(CallbackStatus(kCertName1, true, true))); + ::testing::ElementsAre(CallbackStatus(kCertName1, true, true))); // Reset callback to nullptr. distributor_.SetWatchStatusCallback(nullptr); // Cancel watcher 1 shouldn't trigger any callback. CancelWatch(watcher_state_1); - EXPECT_THAT(GetCallbackQueue(), testing::ElementsAre()); + EXPECT_THAT(GetCallbackQueue(), ::testing::ElementsAre()); } TEST_F(GrpcTlsCertificateDistributorTest, SetKeyMaterialsInCallback) { @@ -586,7 +573,7 @@ TEST_F(GrpcTlsCertificateDistributorTest, SetKeyMaterialsInCallback) { // Check the updates are delivered to watcher 1. EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); CancelWatch(watcher_state_1); @@ -621,7 +608,7 @@ TEST_F(GrpcTlsCertificateDistributorTest, WatchACertInfoWithValidCredentials) { // watcher 1 should receive the credentials right away. EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); CancelWatch(watcher_state_1); @@ -629,13 +616,13 @@ TEST_F(GrpcTlsCertificateDistributorTest, WatchACertInfoWithValidCredentials) { WatcherState* watcher_state_2 = MakeWatcher(kRootCert2Name, absl::nullopt); // watcher 2 should receive the root credentials right away. EXPECT_THAT(watcher_state_2->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo(kRootCert2Contents, {}))); + ::testing::ElementsAre(CredentialInfo(kRootCert2Contents, {}))); // Register watcher 3. WatcherState* watcher_state_3 = MakeWatcher(absl::nullopt, kIdentityCert2Name); // watcher 3 should received the identity credentials right away. EXPECT_THAT(watcher_state_3->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( "", MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents)))); CancelWatch(watcher_state_2); @@ -652,7 +639,7 @@ TEST_F(GrpcTlsCertificateDistributorTest, kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre( + ::testing::ElementsAre( ErrorInfo(kRootErrorMessage, kIdentityErrorMessage))); // Calling SetErrorForCert on root cert name should call OnError // on watcher 1 again. @@ -661,14 +648,14 @@ TEST_F(GrpcTlsCertificateDistributorTest, absl::nullopt); EXPECT_THAT( watcher_state_1->GetErrorQueue(), - testing::ElementsAre(ErrorInfo(kErrorMessage, kIdentityErrorMessage))); + ::testing::ElementsAre(ErrorInfo(kErrorMessage, kIdentityErrorMessage))); // Calling SetErrorForCert on identity cert name should call OnError // on watcher 1 again. distributor_.SetErrorForCert( kCertName1, absl::nullopt, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kErrorMessage)); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre(ErrorInfo(kErrorMessage, kErrorMessage))); + ::testing::ElementsAre(ErrorInfo(kErrorMessage, kErrorMessage))); distributor_.CancelTlsCertificatesWatch(watcher_state_1->watcher); EXPECT_EQ(watcher_state_1->watcher, nullptr); } @@ -682,18 +669,18 @@ TEST_F(GrpcTlsCertificateDistributorTest, SetErrorForCertForRootOrIdentity) { kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), absl::nullopt); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); + ::testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); // Calling SetErrorForCert on identity name should do nothing. distributor_.SetErrorForCert( kCertName1, absl::nullopt, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); - EXPECT_THAT(watcher_state_1->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_1->GetErrorQueue(), ::testing::ElementsAre()); // Calling SetErrorForCert on both names should still get one OnError call. distributor_.SetErrorForCert( kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); + ::testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); CancelWatch(watcher_state_1); // Register watcher 2. WatcherState* watcher_state_2 = MakeWatcher(absl::nullopt, kCertName1); @@ -703,18 +690,18 @@ TEST_F(GrpcTlsCertificateDistributorTest, SetErrorForCertForRootOrIdentity) { kCertName1, absl::nullopt, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); EXPECT_THAT(watcher_state_2->GetErrorQueue(), - testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); + ::testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); // Calling SetErrorForCert on root name should do nothing. distributor_.SetErrorForCert( kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), absl::nullopt); - EXPECT_THAT(watcher_state_2->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_2->GetErrorQueue(), ::testing::ElementsAre()); // Calling SetErrorForCert on both names should still get one OnError call. distributor_.SetErrorForCert( kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); EXPECT_THAT(watcher_state_2->GetErrorQueue(), - testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); + ::testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); CancelWatch(watcher_state_2); } @@ -728,14 +715,14 @@ TEST_F(GrpcTlsCertificateDistributorTest, WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName2); // Should trigger OnError call right away since kCertName1 has error. EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); + ::testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); // Calling SetErrorForCert on kCertName2 should trigger OnError with both // errors, because kCertName1 also has error. distributor_.SetErrorForCert( kCertName2, absl::nullopt, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre( + ::testing::ElementsAre( ErrorInfo(kRootErrorMessage, kIdentityErrorMessage))); CancelWatch(watcher_state_1); } @@ -750,14 +737,14 @@ TEST_F(GrpcTlsCertificateDistributorTest, WatcherState* watcher_state_1 = MakeWatcher(kCertName2, kCertName1); // Should trigger OnError call right away since kCertName2 has error. EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); + ::testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); // Calling SetErrorForCert on kCertName2 should trigger OnError with both // errors, because kCertName1 also has error. distributor_.SetErrorForCert( kCertName2, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), absl::nullopt); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre( + ::testing::ElementsAre( ErrorInfo(kRootErrorMessage, kIdentityErrorMessage))); CancelWatch(watcher_state_1); } @@ -767,25 +754,25 @@ TEST_F(GrpcTlsCertificateDistributorTest, // Register watcher 1 for kCertName1 as root and kCertName2 as identity. WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName2); // Should not trigger OnError. - EXPECT_THAT(watcher_state_1->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_1->GetErrorQueue(), ::testing::ElementsAre()); // Calling SetErrorForCert on kCertName2 should trigger OnError. distributor_.SetErrorForCert( kCertName2, absl::nullopt, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); + ::testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); CancelWatch(watcher_state_1); // Register watcher 2 for kCertName2 as identity and a non-existing name // kRootCert1Name as root. WatcherState* watcher_state_2 = MakeWatcher(kRootCert1Name, kCertName2); // Should not trigger OnError. - EXPECT_THAT(watcher_state_2->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_2->GetErrorQueue(), ::testing::ElementsAre()); // Calling SetErrorForCert on kCertName2 should trigger OnError. distributor_.SetErrorForCert( kCertName2, absl::nullopt, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); EXPECT_THAT(watcher_state_2->error_queue, - testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); + ::testing::ElementsAre(ErrorInfo("", kIdentityErrorMessage))); CancelWatch(watcher_state_2); } @@ -793,25 +780,25 @@ TEST_F(GrpcTlsCertificateDistributorTest, SetErrorForRootNameWithPreexistingErrorForIdentityName) { WatcherState* watcher_state_1 = MakeWatcher(kCertName2, kCertName1); // Should not trigger OnError. - EXPECT_THAT(watcher_state_1->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_1->GetErrorQueue(), ::testing::ElementsAre()); // Calling SetErrorForCert on kCertName2 should trigger OnError. distributor_.SetErrorForCert( kCertName2, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), absl::nullopt); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); + ::testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); CancelWatch(watcher_state_1); // Register watcher 2 for kCertName2 as root and a non-existing name // kIdentityCert1Name as identity. WatcherState* watcher_state_2 = MakeWatcher(kCertName2, kIdentityCert1Name); // Should not trigger OnError. - EXPECT_THAT(watcher_state_2->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_2->GetErrorQueue(), ::testing::ElementsAre()); // Calling SetErrorForCert on kCertName2 should trigger OnError. distributor_.SetErrorForCert( kCertName2, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), absl::nullopt); EXPECT_THAT(watcher_state_2->GetErrorQueue(), - testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); + ::testing::ElementsAre(ErrorInfo(kRootErrorMessage, ""))); CancelWatch(watcher_state_2); } @@ -825,14 +812,14 @@ TEST_F(GrpcTlsCertificateDistributorTest, kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage), GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage)); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre( + ::testing::ElementsAre( ErrorInfo(kRootErrorMessage, kIdentityErrorMessage))); // When watcher 1 is removed, the cert info entry should be removed. CancelWatch(watcher_state_1); // Register watcher 2 on the same cert name. WatcherState* watcher_state_2 = MakeWatcher(kCertName1, kCertName1); // Should not trigger OnError call on watcher 2 right away. - EXPECT_THAT(watcher_state_2->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_2->GetErrorQueue(), ::testing::ElementsAre()); CancelWatch(watcher_state_2); } @@ -851,11 +838,11 @@ TEST_F(GrpcTlsCertificateDistributorTest, // watcher 1 should receive both the old credentials and the error right away. EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre( + ::testing::ElementsAre( ErrorInfo(kRootErrorMessage, kIdentityErrorMessage))); CancelWatch(watcher_state_1); } @@ -876,10 +863,10 @@ TEST_F(GrpcTlsCertificateDistributorTest, // the previous error is wiped out by a successful update. EXPECT_THAT( watcher_state_1->GetCredentialQueue(), - testing::ElementsAre(CredentialInfo( + ::testing::ElementsAre(CredentialInfo( kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents)))); - EXPECT_THAT(watcher_state_1->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_1->GetErrorQueue(), ::testing::ElementsAre()); CancelWatch(watcher_state_1); } @@ -893,11 +880,11 @@ TEST_F(GrpcTlsCertificateDistributorTest, WatchCertInfoThenInvokeSetError) { MakeWatcher(absl::nullopt, kIdentityCert1Name); distributor_.SetError(GRPC_ERROR_CREATE_FROM_STATIC_STRING(kErrorMessage)); EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre(ErrorInfo(kErrorMessage, kErrorMessage))); + ::testing::ElementsAre(ErrorInfo(kErrorMessage, kErrorMessage))); EXPECT_THAT(watcher_state_2->GetErrorQueue(), - testing::ElementsAre(ErrorInfo(kErrorMessage, ""))); + ::testing::ElementsAre(ErrorInfo(kErrorMessage, ""))); EXPECT_THAT(watcher_state_3->GetErrorQueue(), - testing::ElementsAre(ErrorInfo("", kErrorMessage))); + ::testing::ElementsAre(ErrorInfo("", kErrorMessage))); CancelWatch(watcher_state_1); CancelWatch(watcher_state_2); CancelWatch(watcher_state_3); @@ -915,12 +902,12 @@ TEST_F(GrpcTlsCertificateDistributorTest, WatchErroredCertInfoBySetError) { // Register watcher 3 watching kCertName1 as root and kCertName2 as identity // should not get the error updates. WatcherState* watcher_state_3 = MakeWatcher(kCertName1, kCertName2); - EXPECT_THAT(watcher_state_3->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_3->GetErrorQueue(), ::testing::ElementsAre()); CancelWatch(watcher_state_3); // Register watcher 4 watching kCertName2 as root and kCertName1 as identity // should not get the error updates. WatcherState* watcher_state_4 = MakeWatcher(kCertName2, kCertName1); - EXPECT_THAT(watcher_state_4->GetErrorQueue(), testing::ElementsAre()); + EXPECT_THAT(watcher_state_4->GetErrorQueue(), ::testing::ElementsAre()); CancelWatch(watcher_state_4); } @@ -936,7 +923,7 @@ TEST_F(GrpcTlsCertificateDistributorTest, SetErrorForCertInCallback) { WatcherState* watcher_state_1 = MakeWatcher(cert_name, cert_name); // Check the errors are delivered to watcher 1. EXPECT_THAT(watcher_state_1->GetErrorQueue(), - testing::ElementsAre( + ::testing::ElementsAre( ErrorInfo(kRootErrorMessage, kIdentityErrorMessage))); CancelWatch(watcher_state_1); }; @@ -955,6 +942,8 @@ TEST_F(GrpcTlsCertificateDistributorTest, SetErrorForCertInCallback) { } // namespace testing +} // namespace grpc_core + int main(int argc, char** argv) { grpc::testing::TestEnvironment env(argc, argv); ::testing::InitGoogleTest(&argc, argv); diff --git a/test/core/security/grpc_tls_certificate_provider_test.cc b/test/core/security/grpc_tls_certificate_provider_test.cc new file mode 100644 index 00000000000..b8bebf7ac20 --- /dev/null +++ b/test/core/security/grpc_tls_certificate_provider_test.cc @@ -0,0 +1,509 @@ +// +// 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 "src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include "src/core/lib/gpr/tmpfile.h" +#include "src/core/lib/iomgr/load_file.h" +#include "src/core/lib/slice/slice_internal.h" +#include "test/core/security/tls_utils.h" +#include "test/core/util/test_config.h" + +#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem" +#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem" +#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key" +#define CA_CERT_PATH_2 "src/core/tsi/test_creds/multi-domain.pem" +#define SERVER_CERT_PATH_2 "src/core/tsi/test_creds/server0.pem" +#define SERVER_KEY_PATH_2 "src/core/tsi/test_creds/server0.key" +#define INVALID_PATH "invalid/path" + +namespace grpc_core { + +namespace testing { + +constexpr const char* kCertName = "cert_name"; +constexpr const char* kRootError = "Unable to get latest root certificates."; +constexpr const char* kIdentityError = + "Unable to get latest identity certificates."; + +class GrpcTlsCertificateProviderTest : public ::testing::Test { + protected: + // Forward declaration. + class TlsCertificatesTestWatcher; + + // CredentialInfo contains the parameters when calling OnCertificatesChanged + // of a watcher. When OnCertificatesChanged is invoked, we will push a + // CredentialInfo to the cert_update_queue of state_, and check in each test + // if the status updates are correct. + struct CredentialInfo { + std::string root_certs; + PemKeyCertPairList key_cert_pairs; + CredentialInfo(std::string root, PemKeyCertPairList key_cert) + : root_certs(std::move(root)), key_cert_pairs(std::move(key_cert)) {} + bool operator==(const CredentialInfo& other) const { + return root_certs == other.root_certs && + key_cert_pairs == other.key_cert_pairs; + } + }; + + // ErrorInfo contains the parameters when calling OnError of a watcher. When + // OnError is invoked, we will push a ErrorInfo to the error_queue of state_, + // and check in each test if the status updates are correct. + struct ErrorInfo { + std::string root_cert_str; + std::string identity_cert_str; + ErrorInfo(std::string root, std::string identity) + : root_cert_str(std::move(root)), + identity_cert_str(std::move(identity)) {} + bool operator==(const ErrorInfo& other) const { + return root_cert_str == other.root_cert_str && + identity_cert_str == other.identity_cert_str; + } + }; + + struct WatcherState { + TlsCertificatesTestWatcher* watcher = nullptr; + std::deque cert_update_queue; + std::deque error_queue; + Mutex mu; + + std::deque GetCredentialQueue() { + // We move the data member value so the data member will be re-initiated + // with size 0, and ready for the next check. + MutexLock lock(&mu); + return std::move(cert_update_queue); + } + std::deque GetErrorQueue() { + // We move the data member value so the data member will be re-initiated + // with size 0, and ready for the next check. + MutexLock lock(&mu); + return std::move(error_queue); + } + }; + + class TlsCertificatesTestWatcher : public grpc_tls_certificate_distributor:: + TlsCertificatesWatcherInterface { + public: + // ctor sets state->watcher to this. + explicit TlsCertificatesTestWatcher(WatcherState* state) : state_(state) { + state_->watcher = this; + } + + // dtor sets state->watcher to nullptr. + ~TlsCertificatesTestWatcher() override { state_->watcher = nullptr; } + + void OnCertificatesChanged( + absl::optional root_certs, + absl::optional key_cert_pairs) override { + MutexLock lock(&state_->mu); + std::string updated_root; + if (root_certs.has_value()) { + updated_root = std::string(*root_certs); + } + PemKeyCertPairList updated_identity; + if (key_cert_pairs.has_value()) { + updated_identity = std::move(*key_cert_pairs); + } + state_->cert_update_queue.emplace_back(std::move(updated_root), + std::move(updated_identity)); + } + + void OnError(grpc_error* root_cert_error, + grpc_error* identity_cert_error) override { + MutexLock lock(&state_->mu); + GPR_ASSERT(root_cert_error != GRPC_ERROR_NONE || + identity_cert_error != GRPC_ERROR_NONE); + std::string root_error_str; + std::string identity_error_str; + if (root_cert_error != GRPC_ERROR_NONE) { + grpc_slice root_error_slice; + GPR_ASSERT(grpc_error_get_str( + root_cert_error, GRPC_ERROR_STR_DESCRIPTION, &root_error_slice)); + root_error_str = std::string(StringViewFromSlice(root_error_slice)); + } + if (identity_cert_error != GRPC_ERROR_NONE) { + grpc_slice identity_error_slice; + GPR_ASSERT(grpc_error_get_str(identity_cert_error, + GRPC_ERROR_STR_DESCRIPTION, + &identity_error_slice)); + identity_error_str = + std::string(StringViewFromSlice(identity_error_slice)); + } + state_->error_queue.emplace_back(std::move(root_error_str), + std::move(identity_error_str)); + GRPC_ERROR_UNREF(root_cert_error); + GRPC_ERROR_UNREF(identity_cert_error); + } + + private: + WatcherState* state_; + }; + + void SetUp() override { + root_cert_ = GetFileContents(CA_CERT_PATH); + cert_chain_ = GetFileContents(SERVER_CERT_PATH); + private_key_ = GetFileContents(SERVER_KEY_PATH); + root_cert_2_ = GetFileContents(CA_CERT_PATH_2); + cert_chain_2_ = GetFileContents(SERVER_CERT_PATH_2); + private_key_2_ = GetFileContents(SERVER_KEY_PATH_2); + } + + WatcherState* MakeWatcher( + RefCountedPtr distributor, + absl::optional root_cert_name, + absl::optional identity_cert_name) { + MutexLock lock(&mu_); + distributor_ = distributor; + watchers_.emplace_back(); + // TlsCertificatesTestWatcher ctor takes a pointer to the WatcherState. + // It sets WatcherState::watcher to point to itself. + // The TlsCertificatesTestWatcher dtor will set WatcherState::watcher back + // to nullptr to indicate that it's been destroyed. + auto watcher = + absl::make_unique(&watchers_.back()); + distributor_->WatchTlsCertificates(std::move(watcher), + std::move(root_cert_name), + std::move(identity_cert_name)); + return &watchers_.back(); + } + + void CancelWatch(WatcherState* state) { + MutexLock lock(&mu_); + distributor_->CancelTlsCertificatesWatch(state->watcher); + EXPECT_EQ(state->watcher, nullptr); + } + + std::string root_cert_; + std::string private_key_; + std::string cert_chain_; + std::string root_cert_2_; + std::string private_key_2_; + std::string cert_chain_2_; + RefCountedPtr distributor_; + // Use a std::list<> here to avoid the address invalidation caused by internal + // reallocation of std::vector<>. + std::list watchers_; + // This is to make watchers_ thread-safe. + Mutex mu_; +}; + +TEST_F(GrpcTlsCertificateProviderTest, StaticDataCertificateProviderCreation) { + StaticDataCertificateProvider provider( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())); + // Watcher watching both root and identity certs. + WatcherState* watcher_state_1 = + MakeWatcher(provider.distributor(), kCertName, kCertName); + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), + cert_chain_.c_str())))); + CancelWatch(watcher_state_1); + // Watcher watching only root certs. + WatcherState* watcher_state_2 = + MakeWatcher(provider.distributor(), kCertName, absl::nullopt); + EXPECT_THAT(watcher_state_2->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo(root_cert_, {}))); + CancelWatch(watcher_state_2); + // Watcher watching only identity certs. + WatcherState* watcher_state_3 = + MakeWatcher(provider.distributor(), absl::nullopt, kCertName); + EXPECT_THAT( + watcher_state_3->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + "", MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())))); + CancelWatch(watcher_state_3); +} + +TEST_F(GrpcTlsCertificateProviderTest, + FileWatcherCertificateProviderWithGoodPaths) { + FileWatcherCertificateProvider provider(SERVER_KEY_PATH, SERVER_CERT_PATH, + CA_CERT_PATH, 1); + // Watcher watching both root and identity certs. + WatcherState* watcher_state_1 = + MakeWatcher(provider.distributor(), kCertName, kCertName); + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), + cert_chain_.c_str())))); + CancelWatch(watcher_state_1); + // Watcher watching only root certs. + WatcherState* watcher_state_2 = + MakeWatcher(provider.distributor(), kCertName, absl::nullopt); + EXPECT_THAT(watcher_state_2->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo(root_cert_, {}))); + CancelWatch(watcher_state_2); + // Watcher watching only identity certs. + WatcherState* watcher_state_3 = + MakeWatcher(provider.distributor(), absl::nullopt, kCertName); + EXPECT_THAT( + watcher_state_3->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + "", MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())))); + CancelWatch(watcher_state_3); +} + +TEST_F(GrpcTlsCertificateProviderTest, + FileWatcherCertificateProviderWithBadPaths) { + FileWatcherCertificateProvider provider(INVALID_PATH, INVALID_PATH, + INVALID_PATH, 1); + // Watcher watching both root and identity certs. + WatcherState* watcher_state_1 = + MakeWatcher(provider.distributor(), kCertName, kCertName); + EXPECT_THAT(watcher_state_1->GetErrorQueue(), + ::testing::ElementsAre(ErrorInfo(kRootError, kIdentityError))); + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), ::testing::ElementsAre()); + CancelWatch(watcher_state_1); + // Watcher watching only root certs. + WatcherState* watcher_state_2 = + MakeWatcher(provider.distributor(), kCertName, absl::nullopt); + EXPECT_THAT(watcher_state_2->GetErrorQueue(), + ::testing::ElementsAre(ErrorInfo(kRootError, ""))); + EXPECT_THAT(watcher_state_2->GetCredentialQueue(), ::testing::ElementsAre()); + CancelWatch(watcher_state_2); + // Watcher watching only identity certs. + WatcherState* watcher_state_3 = + MakeWatcher(provider.distributor(), absl::nullopt, kCertName); + EXPECT_THAT(watcher_state_3->GetErrorQueue(), + ::testing::ElementsAre(ErrorInfo("", kIdentityError))); + EXPECT_THAT(watcher_state_3->GetCredentialQueue(), ::testing::ElementsAre()); + CancelWatch(watcher_state_3); +} + +// The following tests write credential data to temporary files to test the +// transition behavior of the provider. +TEST_F(GrpcTlsCertificateProviderTest, + FileWatcherCertificateProviderOnBothCertsRefreshed) { + // Create temporary files and copy cert data into them. + TmpFile tmp_root_cert(root_cert_); + TmpFile tmp_identity_key(private_key_); + TmpFile tmp_identity_cert(cert_chain_); + // Create FileWatcherCertificateProvider. + FileWatcherCertificateProvider provider(tmp_identity_key.name(), + tmp_identity_cert.name(), + tmp_root_cert.name(), 1); + WatcherState* watcher_state_1 = + MakeWatcher(provider.distributor(), kCertName, kCertName); + // Expect to see the credential data. + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), + cert_chain_.c_str())))); + // Copy new data to files. + // TODO(ZhenLian): right now it is not completely atomic. Use the real atomic + // update when the directory renaming is added in gpr. + tmp_root_cert.RewriteFile(root_cert_2_); + tmp_identity_key.RewriteFile(private_key_2_); + tmp_identity_cert.RewriteFile(cert_chain_2_); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(2, GPR_TIMESPAN))); + // Expect to see the new credential data. + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_2_, MakeCertKeyPairs(private_key_2_.c_str(), + cert_chain_2_.c_str())))); + // Clean up. + CancelWatch(watcher_state_1); +} + +TEST_F(GrpcTlsCertificateProviderTest, + FileWatcherCertificateProviderOnRootCertsRefreshed) { + // Create temporary files and copy cert data into them. + TmpFile tmp_root_cert(root_cert_); + TmpFile tmp_identity_key(private_key_); + TmpFile tmp_identity_cert(cert_chain_); + // Create FileWatcherCertificateProvider. + FileWatcherCertificateProvider provider(tmp_identity_key.name(), + tmp_identity_cert.name(), + tmp_root_cert.name(), 1); + WatcherState* watcher_state_1 = + MakeWatcher(provider.distributor(), kCertName, kCertName); + // Expect to see the credential data. + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), + cert_chain_.c_str())))); + // Copy new data to files. + // TODO(ZhenLian): right now it is not completely atomic. Use the real atomic + // update when the directory renaming is added in gpr. + tmp_root_cert.RewriteFile(root_cert_2_); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(2, GPR_TIMESPAN))); + // Expect to see the new credential data. + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_2_, MakeCertKeyPairs(private_key_.c_str(), + cert_chain_.c_str())))); + // Clean up. + CancelWatch(watcher_state_1); +} + +TEST_F(GrpcTlsCertificateProviderTest, + FileWatcherCertificateProviderOnIdentityCertsRefreshed) { + // Create temporary files and copy cert data into them. + TmpFile tmp_root_cert(root_cert_); + TmpFile tmp_identity_key(private_key_); + TmpFile tmp_identity_cert(cert_chain_); + // Create FileWatcherCertificateProvider. + FileWatcherCertificateProvider provider(tmp_identity_key.name(), + tmp_identity_cert.name(), + tmp_root_cert.name(), 1); + WatcherState* watcher_state_1 = + MakeWatcher(provider.distributor(), kCertName, kCertName); + // Expect to see the credential data. + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), + cert_chain_.c_str())))); + // Copy new data to files. + // TODO(ZhenLian): right now it is not completely atomic. Use the real atomic + // update when the directory renaming is added in gpr. + tmp_identity_key.RewriteFile(private_key_2_); + tmp_identity_cert.RewriteFile(cert_chain_2_); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(2, GPR_TIMESPAN))); + // Expect to see the new credential data. + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_, MakeCertKeyPairs(private_key_2_.c_str(), + cert_chain_2_.c_str())))); + // Clean up. + CancelWatch(watcher_state_1); +} + +TEST_F(GrpcTlsCertificateProviderTest, + FileWatcherCertificateProviderWithGoodAtFirstThenDeletedBothCerts) { + // Create temporary files and copy cert data into it. + auto tmp_root_cert = absl::make_unique(root_cert_); + auto tmp_identity_key = absl::make_unique(private_key_); + auto tmp_identity_cert = absl::make_unique(cert_chain_); + // Create FileWatcherCertificateProvider. + FileWatcherCertificateProvider provider(tmp_identity_key->name(), + tmp_identity_cert->name(), + tmp_root_cert->name(), 1); + WatcherState* watcher_state_1 = + MakeWatcher(provider.distributor(), kCertName, kCertName); + // The initial data is all good, so we expect to have successful credential + // updates. + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), + cert_chain_.c_str())))); + // Delete TmpFile objects, which will remove the corresponding files. + tmp_root_cert.reset(); + tmp_identity_key.reset(); + tmp_identity_cert.reset(); + // Wait 2 seconds for the provider's refresh thread to read the deleted files. + gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(2, GPR_TIMESPAN))); + // Expect to see errors sent to watchers, and no credential updates. + // We have no ideas on how many errors we will receive, so we only check once. + EXPECT_THAT(watcher_state_1->GetErrorQueue(), + ::testing::Contains(ErrorInfo(kRootError, kIdentityError))); + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), ::testing::ElementsAre()); + // Clean up. + CancelWatch(watcher_state_1); +} + +TEST_F(GrpcTlsCertificateProviderTest, + FileWatcherCertificateProviderWithGoodAtFirstThenDeletedRootCerts) { + // Create temporary files and copy cert data into it. + auto tmp_root_cert = absl::make_unique(root_cert_); + TmpFile tmp_identity_key(private_key_); + TmpFile tmp_identity_cert(cert_chain_); + // Create FileWatcherCertificateProvider. + FileWatcherCertificateProvider provider(tmp_identity_key.name(), + tmp_identity_cert.name(), + tmp_root_cert->name(), 1); + WatcherState* watcher_state_1 = + MakeWatcher(provider.distributor(), kCertName, kCertName); + // The initial data is all good, so we expect to have successful credential + // updates. + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), + cert_chain_.c_str())))); + // Delete root TmpFile object, which will remove the corresponding file. + tmp_root_cert.reset(); + // Wait 2 seconds for the provider's refresh thread to read the deleted files. + gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(2, GPR_TIMESPAN))); + // Expect to see errors sent to watchers, and no credential updates. + // We have no ideas on how many errors we will receive, so we only check once. + EXPECT_THAT(watcher_state_1->GetErrorQueue(), + ::testing::Contains(ErrorInfo(kRootError, ""))); + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), ::testing::ElementsAre()); + // Clean up. + CancelWatch(watcher_state_1); +} + +TEST_F(GrpcTlsCertificateProviderTest, + FileWatcherCertificateProviderWithGoodAtFirstThenDeletedIdentityCerts) { + // Create temporary files and copy cert data into it. + TmpFile tmp_root_cert(root_cert_); + auto tmp_identity_key = absl::make_unique(private_key_); + auto tmp_identity_cert = absl::make_unique(cert_chain_); + // Create FileWatcherCertificateProvider. + FileWatcherCertificateProvider provider(tmp_identity_key->name(), + tmp_identity_cert->name(), + tmp_root_cert.name(), 1); + WatcherState* watcher_state_1 = + MakeWatcher(provider.distributor(), kCertName, kCertName); + // The initial data is all good, so we expect to have successful credential + // updates. + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), + ::testing::ElementsAre(CredentialInfo( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), + cert_chain_.c_str())))); + // Delete identity TmpFile objects, which will remove the corresponding files. + tmp_identity_key.reset(); + tmp_identity_cert.reset(); + // Wait 2 seconds for the provider's refresh thread to read the deleted files. + gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(2, GPR_TIMESPAN))); + // Expect to see errors sent to watchers, and no credential updates. + // We have no ideas on how many errors we will receive, so we only check once. + EXPECT_THAT(watcher_state_1->GetErrorQueue(), + ::testing::Contains(ErrorInfo("", kIdentityError))); + EXPECT_THAT(watcher_state_1->GetCredentialQueue(), ::testing::ElementsAre()); + // Clean up. + CancelWatch(watcher_state_1); +} + +} // namespace testing + +} // namespace grpc_core + +int main(int argc, char** argv) { + grpc::testing::TestEnvironment env(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + grpc_init(); + int ret = RUN_ALL_TESTS(); + grpc_shutdown(); + return ret; +} diff --git a/test/core/security/grpc_tls_credentials_options_test.cc b/test/core/security/grpc_tls_credentials_options_test.cc index dee1d47a92b..932b4788b46 100644 --- a/test/core/security/grpc_tls_credentials_options_test.cc +++ b/test/core/security/grpc_tls_credentials_options_test.cc @@ -24,24 +24,440 @@ #include #include +#include "src/core/lib/gpr/tmpfile.h" #include "src/core/lib/iomgr/load_file.h" +#include "src/core/lib/security/credentials/tls/tls_credentials.h" +#include "src/core/lib/security/security_connector/tls/tls_security_connector.h" +#include "test/core/security/tls_utils.h" #include "test/core/util/test_config.h" #define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem" #define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem" #define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key" +#define CA_CERT_PATH_2 "src/core/tsi/test_creds/multi-domain.pem" +#define SERVER_CERT_PATH_2 "src/core/tsi/test_creds/server0.pem" +#define SERVER_KEY_PATH_2 "src/core/tsi/test_creds/server0.key" +#define INVALID_PATH "invalid/path" + +namespace grpc_core { namespace testing { -TEST(GrpcTlsCredentialsOptionsTest, ErrorDetails) { +class GrpcTlsCredentialsOptionsTest : public ::testing::Test { + protected: + void SetUp() override { + root_cert_ = GetFileContents(CA_CERT_PATH); + cert_chain_ = GetFileContents(SERVER_CERT_PATH); + private_key_ = GetFileContents(SERVER_KEY_PATH); + root_cert_2_ = GetFileContents(CA_CERT_PATH_2); + cert_chain_2_ = GetFileContents(SERVER_CERT_PATH_2); + private_key_2_ = GetFileContents(SERVER_KEY_PATH_2); + } + + std::string root_cert_; + std::string private_key_; + std::string cert_chain_; + std::string root_cert_2_; + std::string private_key_2_; + std::string cert_chain_2_; +}; + +TEST_F(GrpcTlsCredentialsOptionsTest, ErrorDetails) { grpc_tls_error_details error_details; EXPECT_STREQ(error_details.error_details().c_str(), ""); error_details.set_error_details("test error details"); EXPECT_STREQ(error_details.error_details().c_str(), "test error details"); } +// Tests for StaticDataCertificateProvider. +TEST_F(GrpcTlsCredentialsOptionsTest, + ClientOptionsWithStaticDataProviderOnBothCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_watch_identity_pair(true); + options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + grpc_channel_args* new_args = nullptr; + auto connector = credentials->create_security_connector( + nullptr, "random targets", nullptr, &new_args); + grpc_channel_args_destroy(new_args); + ASSERT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_NE(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); + EXPECT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ClientOptionsWithStaticDataProviderOnRootCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + root_cert_, PemKeyCertPairList()); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + grpc_channel_args* new_args = nullptr; + auto connector = credentials->create_security_connector( + nullptr, "random targets", nullptr, &new_args); + grpc_channel_args_destroy(new_args); + ASSERT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_NE(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); + EXPECT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_FALSE(tls_connector->KeyCertPairListForTesting().has_value()); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ClientOptionsWithStaticDataProviderOnNotProvidedCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + "", MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + grpc_channel_args* new_args = nullptr; + auto connector = credentials->create_security_connector( + nullptr, "random targets", nullptr, &new_args); + grpc_channel_args_destroy(new_args); + ASSERT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_EQ(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ServerOptionsWithStaticDataProviderOnBothCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + root_cert_, MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_watch_identity_pair(true); + options->set_cert_request_type( + GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + auto connector = credentials->create_security_connector(); + ASSERT_NE(connector, nullptr); + TlsServerSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_NE(tls_connector->ServerHandshakerFactoryForTesting(), nullptr); + EXPECT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ServerOptionsWithStaticDataProviderOnIdentityCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + "", MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())); + options->set_certificate_provider(std::move(provider)); + options->set_watch_identity_pair(true); + options->set_cert_request_type(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + auto connector = credentials->create_security_connector(); + ASSERT_NE(connector, nullptr); + TlsServerSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_NE(tls_connector->ServerHandshakerFactoryForTesting(), nullptr); + EXPECT_FALSE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ServerOptionsWithStaticDataProviderOnNotProvidedCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + root_cert_, PemKeyCertPairList()); + options->set_certificate_provider(std::move(provider)); + options->set_watch_identity_pair(true); + options->set_cert_request_type(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + auto connector = credentials->create_security_connector(); + ASSERT_NE(connector, nullptr); + TlsServerSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_EQ(tls_connector->ServerHandshakerFactoryForTesting(), nullptr); +} + +//// Tests for FileWatcherCertificateProvider. +TEST_F(GrpcTlsCredentialsOptionsTest, + ClientOptionsWithCertWatcherProviderOnBothCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_watch_identity_pair(true); + options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + grpc_channel_args* new_args = nullptr; + auto connector = credentials->create_security_connector( + nullptr, "random targets", nullptr, &new_args); + grpc_channel_args_destroy(new_args); + ASSERT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_NE(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); + EXPECT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ClientOptionsWithCertWatcherProviderOnRootCerts) { + auto options = MakeRefCounted(); + auto provider = + MakeRefCounted("", "", CA_CERT_PATH, 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + grpc_channel_args* new_args = nullptr; + auto connector = credentials->create_security_connector( + nullptr, "random targets", nullptr, &new_args); + grpc_channel_args_destroy(new_args); + ASSERT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_NE(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); + EXPECT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_FALSE(tls_connector->KeyCertPairListForTesting().has_value()); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ClientOptionsWithCertWatcherProviderOnNotProvidedCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + SERVER_KEY_PATH, SERVER_CERT_PATH, "", 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + grpc_channel_args* new_args = nullptr; + auto connector = credentials->create_security_connector( + nullptr, "random targets", nullptr, &new_args); + grpc_channel_args_destroy(new_args); + ASSERT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_EQ(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ClientOptionsWithCertWatcherProviderOnBadTrustCerts) { + auto options = MakeRefCounted(); + auto provider = + MakeRefCounted("", "", INVALID_PATH, 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + grpc_channel_args* new_args = nullptr; + auto connector = credentials->create_security_connector( + nullptr, "random targets", nullptr, &new_args); + grpc_channel_args_destroy(new_args); + ASSERT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_EQ(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ServerOptionsWithCertWatcherProviderOnBothCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_watch_identity_pair(true); + options->set_cert_request_type( + GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + auto connector = credentials->create_security_connector(); + ASSERT_NE(connector, nullptr); + TlsServerSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_NE(tls_connector->ServerHandshakerFactoryForTesting(), nullptr); + EXPECT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ServerOptionsWithCertWatcherProviderOnIdentityCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + SERVER_KEY_PATH, SERVER_CERT_PATH, "", 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_identity_pair(true); + options->set_cert_request_type(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + auto connector = credentials->create_security_connector(); + ASSERT_NE(connector, nullptr); + TlsServerSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_NE(tls_connector->ServerHandshakerFactoryForTesting(), nullptr); + EXPECT_FALSE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ServerOptionsWithCertWatcherProviderOnNotProvidedCerts) { + auto options = MakeRefCounted(); + auto provider = + MakeRefCounted("", "", CA_CERT_PATH, 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_identity_pair(true); + options->set_cert_request_type(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + auto connector = credentials->create_security_connector(); + ASSERT_NE(connector, nullptr); + TlsServerSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_EQ(tls_connector->ServerHandshakerFactoryForTesting(), nullptr); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ServerOptionsWithCertWatcherProviderOnBadIdentityCerts) { + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + INVALID_PATH, INVALID_PATH, "", 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_identity_pair(true); + options->set_cert_request_type(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + auto connector = credentials->create_security_connector(); + ASSERT_NE(connector, nullptr); + TlsServerSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_EQ(tls_connector->ServerHandshakerFactoryForTesting(), nullptr); +} + +// The following tests write credential data to temporary files to test the +// transition behavior of the provider. +TEST_F(GrpcTlsCredentialsOptionsTest, + ClientOptionsWithCertWatcherProviderOnCertificateRefreshed) { + // Create temporary files and copy cert data into them. + TmpFile tmp_root_cert(root_cert_); + TmpFile tmp_identity_key(private_key_); + TmpFile tmp_identity_cert(cert_chain_); + // Create ClientOptions using FileWatcherCertificateProvider. + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + tmp_identity_key.name(), tmp_identity_cert.name(), tmp_root_cert.name(), + 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_watch_identity_pair(true); + options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + grpc_channel_args* new_args = nullptr; + auto connector = credentials->create_security_connector( + nullptr, "random targets", nullptr, &new_args); + grpc_channel_args_destroy(new_args); + ASSERT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + // Expect to see the credential data. + EXPECT_NE(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); + ASSERT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_EQ(tls_connector->RootCertsForTesting(), root_cert_); + ASSERT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); + EXPECT_EQ(tls_connector->KeyCertPairListForTesting(), + MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())); + // Copy new data to files. + // TODO(ZhenLian): right now it is not completely atomic. Use the real atomic + // update when the directory renaming is added in gpr. + tmp_root_cert.RewriteFile(root_cert_2_); + tmp_identity_key.RewriteFile(private_key_2_); + tmp_identity_cert.RewriteFile(cert_chain_2_); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(2, GPR_TIMESPAN))); + // Expect to see new credential data loaded by the security connector. + EXPECT_NE(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); + ASSERT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_EQ(tls_connector->RootCertsForTesting(), root_cert_2_); + ASSERT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); + EXPECT_EQ(tls_connector->KeyCertPairListForTesting(), + MakeCertKeyPairs(private_key_2_.c_str(), cert_chain_2_.c_str())); +} + +TEST_F(GrpcTlsCredentialsOptionsTest, + ClientOptionsWithCertWatcherProviderOnDeletedFiles) { + // Create temporary files and copy cert data into it. + auto tmp_root_cert = absl::make_unique(root_cert_); + auto tmp_identity_key = absl::make_unique(private_key_); + auto tmp_identity_cert = absl::make_unique(cert_chain_); + // Create ClientOptions using FileWatcherCertificateProvider. + auto options = MakeRefCounted(); + auto provider = MakeRefCounted( + tmp_identity_key->name(), tmp_identity_cert->name(), + tmp_root_cert->name(), 1); + options->set_certificate_provider(std::move(provider)); + options->set_watch_root_cert(true); + options->set_watch_identity_pair(true); + options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto credentials = MakeRefCounted(options); + ASSERT_NE(credentials, nullptr); + grpc_channel_args* new_args = nullptr; + auto connector = credentials->create_security_connector( + nullptr, "random targets", nullptr, &new_args); + grpc_channel_args_destroy(new_args); + ASSERT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + // The initial data is all good, so we expect to have successful credential + // updates. + EXPECT_NE(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); + ASSERT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_EQ(tls_connector->RootCertsForTesting(), root_cert_); + ASSERT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); + EXPECT_EQ(tls_connector->KeyCertPairListForTesting(), + MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())); + // Delete TmpFile objects, which will remove the corresponding files. + tmp_root_cert.reset(); + tmp_identity_key.reset(); + tmp_identity_cert.reset(); + // Wait 2 seconds for the provider's refresh thread to read the deleted files. + gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(2, GPR_TIMESPAN))); + // It's a bit hard to test if errors are sent to the security connector, + // because the security connector simply logs the error. We will see the err + // messages if we open the log. + // The old certs should still being used. + EXPECT_NE(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); + ASSERT_TRUE(tls_connector->RootCertsForTesting().has_value()); + EXPECT_EQ(tls_connector->RootCertsForTesting(), root_cert_); + ASSERT_TRUE(tls_connector->KeyCertPairListForTesting().has_value()); + EXPECT_EQ(tls_connector->KeyCertPairListForTesting(), + MakeCertKeyPairs(private_key_.c_str(), cert_chain_.c_str())); +} + } // namespace testing +} // namespace grpc_core + int main(int argc, char** argv) { grpc::testing::TestEnvironment env(argc, argv); ::testing::InitGoogleTest(&argc, argv); diff --git a/test/core/security/tls_utils.cc b/test/core/security/tls_utils.cc new file mode 100644 index 00000000000..4f56ed21cf8 --- /dev/null +++ b/test/core/security/tls_utils.cc @@ -0,0 +1,83 @@ +// +// 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 "test/core/security/tls_utils.h" + +#include "src/core/lib/gpr/tmpfile.h" +#include "src/core/lib/iomgr/load_file.h" +#include "src/core/lib/slice/slice_internal.h" + +namespace grpc_core { + +namespace testing { + +TmpFile::TmpFile(absl::string_view credential_data) { + name_ = CreateTmpFileAndWriteData(credential_data); + GPR_ASSERT(!name_.empty()); +} + +TmpFile::~TmpFile() { GPR_ASSERT(remove(name_.c_str()) == 0); } + +void TmpFile::RewriteFile(absl::string_view credential_data) { + // Create a new file containing new data. + std::string new_name = CreateTmpFileAndWriteData(credential_data); + GPR_ASSERT(!new_name.empty()); + // Remove the old file. + GPR_ASSERT(remove(name_.c_str()) == 0); + // Rename the new file to the original name. + GPR_ASSERT(rename(new_name.c_str(), name_.c_str()) == 0); +} + +std::string TmpFile::CreateTmpFileAndWriteData( + absl::string_view credential_data) { + char* name = nullptr; + FILE* file_descriptor = gpr_tmpfile("GrpcTlsCertificateProviderTest", &name); + GPR_ASSERT(fwrite(credential_data.data(), 1, credential_data.size(), + file_descriptor) == credential_data.size()); + GPR_ASSERT(fclose(file_descriptor) == 0); + GPR_ASSERT(file_descriptor != nullptr); + GPR_ASSERT(name != nullptr); + std::string name_to_return = name; + gpr_free(name); + return name_to_return; +} + +PemKeyCertPairList MakeCertKeyPairs(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); + PemKeyCertPairList pem_key_cert_pairs; + pem_key_cert_pairs.emplace_back(ssl_pair); + return pem_key_cert_pairs; +} + +std::string GetFileContents(const char* path) { + grpc_slice slice = grpc_empty_slice(); + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", grpc_load_file(path, 0, &slice))); + std::string credential = std::string(StringViewFromSlice(slice)); + grpc_slice_unref(slice); + return credential; +} + +} // namespace testing + +} // namespace grpc_core diff --git a/test/core/security/tls_utils.h b/test/core/security/tls_utils.h new file mode 100644 index 00000000000..94d29efa713 --- /dev/null +++ b/test/core/security/tls_utils.h @@ -0,0 +1,47 @@ +// +// 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 "src/core/lib/security/security_connector/ssl_utils.h" + +namespace grpc_core { + +namespace testing { + +class TmpFile { + public: + // Create a temporary file with |credential_data| written in. + explicit TmpFile(absl::string_view credential_data); + + ~TmpFile(); + + const std::string& name() { return name_; } + + // Rewrite |credential_data| to the temporary file, in an atomic way. + void RewriteFile(absl::string_view credential_data); + + private: + std::string CreateTmpFileAndWriteData(absl::string_view credential_data); + + std::string name_; +}; + +PemKeyCertPairList MakeCertKeyPairs(const char* private_key, const char* certs); + +std::string GetFileContents(const char* path); + +} // namespace testing + +} // namespace grpc_core diff --git a/test/core/surface/public_headers_must_be_c89.c b/test/core/surface/public_headers_must_be_c89.c index 4b821bebec1..cd0095fb951 100644 --- a/test/core/surface/public_headers_must_be_c89.c +++ b/test/core/surface/public_headers_must_be_c89.c @@ -206,6 +206,7 @@ int main(int argc, char **argv) { printf("%lx", (unsigned long) grpc_tls_identity_pairs_add_pair); printf("%lx", (unsigned long) grpc_tls_identity_pairs_destroy); printf("%lx", (unsigned long) grpc_tls_certificate_provider_static_data_create); + printf("%lx", (unsigned long) grpc_tls_certificate_provider_file_watcher_create); printf("%lx", (unsigned long) grpc_tls_certificate_provider_release); printf("%lx", (unsigned long) grpc_tls_credentials_options_create); printf("%lx", (unsigned long) grpc_tls_credentials_options_set_cert_request_type); diff --git a/test/cpp/client/BUILD b/test/cpp/client/BUILD index ce0f6d39f9f..c62c6332dbc 100644 --- a/test/cpp/client/BUILD +++ b/test/cpp/client/BUILD @@ -21,6 +21,11 @@ grpc_package(name = "test/cpp/client") grpc_cc_test( name = "credentials_test", srcs = ["credentials_test.cc"], + data = [ + "//src/core/tsi/test_creds:ca.pem", + "//src/core/tsi/test_creds:server1.key", + "//src/core/tsi/test_creds:server1.pem", + ], external_deps = [ "gtest", ], diff --git a/test/cpp/client/credentials_test.cc b/test/cpp/client/credentials_test.cc index 0d9eaf01e5c..b2ec888ae53 100644 --- a/test/cpp/client/credentials_test.cc +++ b/test/cpp/client/credentials_test.cc @@ -32,6 +32,10 @@ #include "src/cpp/client/secure_credentials.h" #include "src/cpp/common/tls_credentials_options_util.h" +#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem" +#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem" +#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key" + namespace { constexpr const char* kRootCertName = "root_cert_name"; @@ -40,6 +44,7 @@ constexpr const char* kIdentityCertName = "identity_cert_name"; constexpr const char* kIdentityCertPrivateKey = "identity_private_key"; constexpr const char* kIdentityCertContents = "identity_cert_contents"; +using ::grpc::experimental::FileWatcherCertificateProvider; using ::grpc::experimental::StaticDataCertificateProvider; using ::grpc::experimental::TlsServerAuthorizationCheckArg; using ::grpc::experimental::TlsServerAuthorizationCheckConfig; @@ -407,6 +412,52 @@ TEST(CredentialsTest, GPR_ASSERT(channel_credentials.get() != nullptr); } +TEST( + CredentialsTest, + TlsChannelCredentialsWithFileWatcherCertificateProviderLoadingRootAndIdentity) { + auto certificate_provider = std::make_shared( + SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1); + grpc::experimental::TlsChannelCredentialsOptions options( + certificate_provider); + options.watch_root_certs(); + options.set_root_cert_name(kRootCertName); + options.watch_identity_key_cert_pairs(); + options.set_identity_cert_name(kIdentityCertName); + options.set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto test_server_authorization_check = + std::make_shared(); + auto server_authorization_check_config = + std::make_shared( + test_server_authorization_check); + options.set_server_authorization_check_config( + server_authorization_check_config); + auto channel_credentials = grpc::experimental::TlsCredentials(options); + GPR_ASSERT(channel_credentials.get() != nullptr); +} + +// ChannelCredentials should always have root credential presented. +// Otherwise the system root certificates will be loaded, which will cause +// failure in some tests under MacOS/Windows. +TEST(CredentialsTest, + TlsChannelCredentialsWithFileWatcherCertificateProviderLoadingRootOnly) { + auto certificate_provider = + std::make_shared(CA_CERT_PATH, 1); + grpc::experimental::TlsChannelCredentialsOptions options( + certificate_provider); + options.watch_root_certs(); + options.set_root_cert_name(kRootCertName); + options.set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION); + auto test_server_authorization_check = + std::make_shared(); + auto server_authorization_check_config = + std::make_shared( + test_server_authorization_check); + options.set_server_authorization_check_config( + server_authorization_check_config); + auto channel_credentials = grpc::experimental::TlsCredentials(options); + GPR_ASSERT(channel_credentials.get() != nullptr); +} + TEST(CredentialsTest, TlsServerAuthorizationCheckConfigErrorMessages) { std::shared_ptr config( new TlsServerAuthorizationCheckConfig(nullptr)); diff --git a/test/cpp/server/BUILD b/test/cpp/server/BUILD index c2ccd8a4cf1..cb1972a1561 100644 --- a/test/cpp/server/BUILD +++ b/test/cpp/server/BUILD @@ -63,6 +63,11 @@ grpc_cc_test( grpc_cc_test( name = "credentials_test", srcs = ["credentials_test.cc"], + data = [ + "//src/core/tsi/test_creds:ca.pem", + "//src/core/tsi/test_creds:server1.key", + "//src/core/tsi/test_creds:server1.pem", + ], external_deps = [ "gtest", ], diff --git a/test/cpp/server/credentials_test.cc b/test/cpp/server/credentials_test.cc index 8e7fc1719a9..3c8dcf7aff9 100644 --- a/test/cpp/server/credentials_test.cc +++ b/test/cpp/server/credentials_test.cc @@ -27,6 +27,10 @@ #include "test/core/util/port.h" #include "test/core/util/test_config.h" +#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem" +#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem" +#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key" + namespace { constexpr const char* kRootCertName = "root_cert_name"; @@ -35,6 +39,7 @@ constexpr const char* kIdentityCertName = "identity_cert_name"; constexpr const char* kIdentityCertPrivateKey = "identity_private_key"; constexpr const char* kIdentityCertContents = "identity_cert_contents"; +using ::grpc::experimental::FileWatcherCertificateProvider; using ::grpc::experimental::StaticDataCertificateProvider; } // namespace @@ -86,6 +91,38 @@ TEST(CredentialsTest, GPR_ASSERT(server_credentials.get() != nullptr); } +TEST( + CredentialsTest, + TlsServerCredentialsWithFileWatcherCertificateProviderLoadingRootAndIdentity) { + auto certificate_provider = std::make_shared( + SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1); + grpc::experimental::TlsServerCredentialsOptions options(certificate_provider); + options.watch_root_certs(); + options.set_root_cert_name(kRootCertName); + options.watch_identity_key_cert_pairs(); + options.set_identity_cert_name(kIdentityCertName); + options.set_cert_request_type( + GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY); + auto server_credentials = grpc::experimental::TlsServerCredentials(options); + GPR_ASSERT(server_credentials.get() != nullptr); +} + +// ServerCredentials should always have identity credential presented. +// Otherwise gRPC stack will fail. +TEST( + CredentialsTest, + TlsServerCredentialsWithFileWatcherCertificateProviderLoadingIdentityOnly) { + auto certificate_provider = std::make_shared( + SERVER_KEY_PATH, SERVER_CERT_PATH, 1); + grpc::experimental::TlsServerCredentialsOptions options(certificate_provider); + options.watch_identity_key_cert_pairs(); + options.set_identity_cert_name(kIdentityCertName); + options.set_cert_request_type( + GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY); + auto server_credentials = grpc::experimental::TlsServerCredentials(options); + GPR_ASSERT(server_credentials.get() != nullptr); +} + } // namespace } // namespace testing } // namespace grpc diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index 61ac2ff7ae8..48bb34f5a55 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -4643,6 +4643,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": "grpc_tls_certificate_provider_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": true + }, { "args": [], "benchmark": false,