Add tls_certificate_distributor implementation

pull/23572/head
ZhenLian 4 years ago
parent f60d5ef011
commit 1a275d7eab
  1. 2
      BUILD
  2. 2
      BUILD.gn
  3. 41
      CMakeLists.txt
  4. 2
      Makefile
  5. 15
      build_autogenerated.yaml
  6. 1
      config.m4
  7. 1
      config.w32
  8. 2
      gRPC-C++.podspec
  9. 3
      gRPC-Core.podspec
  10. 2
      grpc.gemspec
  11. 1
      grpc.gyp
  12. 2
      package.xml
  13. 321
      src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc
  14. 214
      src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h
  15. 5
      src/core/lib/security/security_connector/ssl_utils.h
  16. 1
      src/python/grpcio/grpc_core_dependencies.py
  17. 13
      test/core/security/BUILD
  18. 968
      test/core/security/grpc_tls_certificate_distributor_test.cc
  19. 2
      tools/doxygen/Doxyfile.c++.internal
  20. 2
      tools/doxygen/Doxyfile.core.internal
  21. 24
      tools/run_tests/generated/tests.json

@ -1712,6 +1712,7 @@ grpc_cc_library(
"src/core/lib/security/credentials/oauth2/oauth2_credentials.cc",
"src/core/lib/security/credentials/plugin/plugin_credentials.cc",
"src/core/lib/security/credentials/ssl/ssl_credentials.cc",
"src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc",
"src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc",
"src/core/lib/security/credentials/tls/tls_credentials.cc",
"src/core/lib/security/security_connector/alts/alts_security_connector.cc",
@ -1750,6 +1751,7 @@ grpc_cc_library(
"src/core/lib/security/credentials/oauth2/oauth2_credentials.h",
"src/core/lib/security/credentials/plugin/plugin_credentials.h",
"src/core/lib/security/credentials/ssl/ssl_credentials.h",
"src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h",
"src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h",
"src/core/lib/security/credentials/tls/tls_credentials.h",
"src/core/lib/security/security_connector/alts/alts_security_connector.h",

@ -837,6 +837,8 @@ config("grpc_config") {
"src/core/lib/security/credentials/plugin/plugin_credentials.h",
"src/core/lib/security/credentials/ssl/ssl_credentials.cc",
"src/core/lib/security/credentials/ssl/ssl_credentials.h",
"src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc",
"src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h",
"src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc",
"src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h",
"src/core/lib/security/credentials/tls/tls_credentials.cc",

@ -821,6 +821,7 @@ if(gRPC_BUILD_TESTS)
endif()
add_dependencies(buildtests_cxx global_config_test)
add_dependencies(buildtests_cxx grpc_cli)
add_dependencies(buildtests_cxx grpc_tls_certificate_distributor_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)
@ -1748,6 +1749,7 @@ add_library(grpc
src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
src/core/lib/security/credentials/plugin/plugin_credentials.cc
src/core/lib/security/credentials/ssl/ssl_credentials.cc
src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc
src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc
src/core/lib/security/credentials/tls/tls_credentials.cc
src/core/lib/security/security_connector/alts/alts_security_connector.cc
@ -11361,6 +11363,45 @@ if(gRPC_INSTALL)
)
endif()
endif()
if(gRPC_BUILD_TESTS)
add_executable(grpc_tls_certificate_distributor_test
test/core/security/grpc_tls_certificate_distributor_test.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(grpc_tls_certificate_distributor_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_distributor_test
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
grpc_test_util
grpc
gpr
address_sorting
upb
${_gRPC_GFLAGS_LIBRARIES}
)
endif()
if(gRPC_BUILD_TESTS)

@ -2155,6 +2155,7 @@ LIBGRPC_SRC = \
src/core/lib/security/credentials/oauth2/oauth2_credentials.cc \
src/core/lib/security/credentials/plugin/plugin_credentials.cc \
src/core/lib/security/credentials/ssl/ssl_credentials.cc \
src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc \
src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc \
src/core/lib/security/credentials/tls/tls_credentials.cc \
src/core/lib/security/security_connector/alts/alts_security_connector.cc \
@ -4616,6 +4617,7 @@ src/core/lib/security/credentials/local/local_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/oauth2/oauth2_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/plugin/plugin_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/ssl/ssl_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/tls/tls_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/security_connector/alts/alts_security_connector.cc: $(OPENSSL_DEP)

@ -676,6 +676,7 @@ libs:
- src/core/lib/security/credentials/oauth2/oauth2_credentials.h
- src/core/lib/security/credentials/plugin/plugin_credentials.h
- src/core/lib/security/credentials/ssl/ssl_credentials.h
- src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h
- src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h
- src/core/lib/security/credentials/tls/tls_credentials.h
- src/core/lib/security/security_connector/alts/alts_security_connector.h
@ -1092,6 +1093,7 @@ libs:
- src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
- src/core/lib/security/credentials/plugin/plugin_credentials.cc
- src/core/lib/security/credentials/ssl/ssl_credentials.cc
- src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc
- src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc
- src/core/lib/security/credentials/tls/tls_credentials.cc
- src/core/lib/security/security_connector/alts/alts_security_connector.cc
@ -5899,6 +5901,19 @@ targets:
deps:
- grpc_plugin_support
secure: false
- name: grpc_tls_certificate_distributor_test
gtest: true
build: test
language: c++
headers: []
src:
- test/core/security/grpc_tls_certificate_distributor_test.cc
deps:
- grpc_test_util
- grpc
- gpr
- address_sorting
- upb
- name: grpc_tls_credentials_options_test
gtest: true
build: test

@ -417,6 +417,7 @@ if test "$PHP_GRPC" != "no"; then
src/core/lib/security/credentials/oauth2/oauth2_credentials.cc \
src/core/lib/security/credentials/plugin/plugin_credentials.cc \
src/core/lib/security/credentials/ssl/ssl_credentials.cc \
src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc \
src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc \
src/core/lib/security/credentials/tls/tls_credentials.cc \
src/core/lib/security/security_connector/alts/alts_security_connector.cc \

@ -384,6 +384,7 @@ if (PHP_GRPC != "no") {
"src\\core\\lib\\security\\credentials\\oauth2\\oauth2_credentials.cc " +
"src\\core\\lib\\security\\credentials\\plugin\\plugin_credentials.cc " +
"src\\core\\lib\\security\\credentials\\ssl\\ssl_credentials.cc " +
"src\\core\\lib\\security\\credentials\\tls\\grpc_tls_certificate_distributor.cc " +
"src\\core\\lib\\security\\credentials\\tls\\grpc_tls_credentials_options.cc " +
"src\\core\\lib\\security\\credentials\\tls\\tls_credentials.cc " +
"src\\core\\lib\\security\\security_connector\\alts\\alts_security_connector.cc " +

@ -537,6 +537,7 @@ Pod::Spec.new do |s|
'src/core/lib/security/credentials/oauth2/oauth2_credentials.h',
'src/core/lib/security/credentials/plugin/plugin_credentials.h',
'src/core/lib/security/credentials/ssl/ssl_credentials.h',
'src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h',
'src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h',
'src/core/lib/security/credentials/tls/tls_credentials.h',
'src/core/lib/security/security_connector/alts/alts_security_connector.h',
@ -1036,6 +1037,7 @@ Pod::Spec.new do |s|
'src/core/lib/security/credentials/oauth2/oauth2_credentials.h',
'src/core/lib/security/credentials/plugin/plugin_credentials.h',
'src/core/lib/security/credentials/ssl/ssl_credentials.h',
'src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h',
'src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h',
'src/core/lib/security/credentials/tls/tls_credentials.h',
'src/core/lib/security/security_connector/alts/alts_security_connector.h',

@ -892,6 +892,8 @@ Pod::Spec.new do |s|
'src/core/lib/security/credentials/plugin/plugin_credentials.h',
'src/core/lib/security/credentials/ssl/ssl_credentials.cc',
'src/core/lib/security/credentials/ssl/ssl_credentials.h',
'src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc',
'src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h',
'src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc',
'src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h',
'src/core/lib/security/credentials/tls/tls_credentials.cc',
@ -1448,6 +1450,7 @@ Pod::Spec.new do |s|
'src/core/lib/security/credentials/oauth2/oauth2_credentials.h',
'src/core/lib/security/credentials/plugin/plugin_credentials.h',
'src/core/lib/security/credentials/ssl/ssl_credentials.h',
'src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h',
'src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h',
'src/core/lib/security/credentials/tls/tls_credentials.h',
'src/core/lib/security/security_connector/alts/alts_security_connector.h',

@ -810,6 +810,8 @@ Gem::Specification.new do |s|
s.files += %w( src/core/lib/security/credentials/plugin/plugin_credentials.h )
s.files += %w( src/core/lib/security/credentials/ssl/ssl_credentials.cc )
s.files += %w( src/core/lib/security/credentials/ssl/ssl_credentials.h )
s.files += %w( src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc )
s.files += %w( src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h )
s.files += %w( src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc )
s.files += %w( src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h )
s.files += %w( src/core/lib/security/credentials/tls/tls_credentials.cc )

@ -781,6 +781,7 @@
'src/core/lib/security/credentials/oauth2/oauth2_credentials.cc',
'src/core/lib/security/credentials/plugin/plugin_credentials.cc',
'src/core/lib/security/credentials/ssl/ssl_credentials.cc',
'src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc',
'src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc',
'src/core/lib/security/credentials/tls/tls_credentials.cc',
'src/core/lib/security/security_connector/alts/alts_security_connector.cc',

@ -790,6 +790,8 @@
<file baseinstalldir="/" name="src/core/lib/security/credentials/plugin/plugin_credentials.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/ssl/ssl_credentials.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/ssl/ssl_credentials.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/tls/tls_credentials.cc" role="src" />

@ -0,0 +1,321 @@
//
// 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 <grpc/support/port_platform.h>
#include "src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h"
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <stdlib.h>
#include <string.h>
void grpc_tls_certificate_distributor::SetKeyMaterials(
const std::string& cert_name, absl::optional<std::string> pem_root_certs,
absl::optional<PemKeyCertPairList> pem_key_cert_pairs) {
GPR_ASSERT(pem_root_certs.has_value() || pem_key_cert_pairs.has_value());
grpc_core::MutexLock lock(&mu_);
auto& cert_info = certificate_info_map_[cert_name];
if (pem_root_certs.has_value()) {
// Successful credential updates will clear any pre-existing error.
cert_info.SetRootError(GRPC_ERROR_NONE);
for (auto* watcher_ptr : cert_info.root_cert_watchers) {
GPR_ASSERT(watcher_ptr != nullptr);
const auto watcher_it = watchers_.find(watcher_ptr);
GPR_ASSERT(watcher_it != watchers_.end());
GPR_ASSERT(watcher_it->second.root_cert_name.has_value());
absl::optional<PemKeyCertPairList> pem_key_cert_pairs_to_report;
if (pem_key_cert_pairs.has_value() &&
watcher_it->second.identity_cert_name == cert_name) {
pem_key_cert_pairs_to_report = pem_key_cert_pairs;
} else if (watcher_it->second.identity_cert_name.has_value()) {
auto& identity_cert_info =
certificate_info_map_[*watcher_it->second.identity_cert_name];
pem_key_cert_pairs_to_report = identity_cert_info.pem_key_cert_pairs;
}
watcher_ptr->OnCertificatesChanged(
pem_root_certs, std::move(pem_key_cert_pairs_to_report));
}
cert_info.pem_root_certs = std::move(*pem_root_certs);
}
if (pem_key_cert_pairs.has_value()) {
// Successful credential updates will clear any pre-existing error.
cert_info.SetIdentityError(GRPC_ERROR_NONE);
for (const auto watcher_ptr : cert_info.identity_cert_watchers) {
GPR_ASSERT(watcher_ptr != nullptr);
const auto watcher_it = watchers_.find(watcher_ptr);
GPR_ASSERT(watcher_it != watchers_.end());
GPR_ASSERT(watcher_it->second.identity_cert_name.has_value());
absl::optional<absl::string_view> pem_root_certs_to_report;
if (pem_root_certs.has_value() &&
watcher_it->second.root_cert_name == cert_name) {
// In this case, We've already sent the credential updates at the time
// when checking pem_root_certs, so we will skip here.
continue;
} else if (watcher_it->second.root_cert_name.has_value()) {
auto& root_cert_info =
certificate_info_map_[*watcher_it->second.root_cert_name];
pem_root_certs_to_report = root_cert_info.pem_root_certs;
}
watcher_ptr->OnCertificatesChanged(pem_root_certs_to_report,
pem_key_cert_pairs);
}
cert_info.pem_key_cert_pairs = std::move(*pem_key_cert_pairs);
}
}
bool grpc_tls_certificate_distributor::HasRootCerts(
const std::string& root_cert_name) {
grpc_core::MutexLock lock(&mu_);
const auto it = certificate_info_map_.find(root_cert_name);
return it != certificate_info_map_.end() &&
!it->second.pem_root_certs.empty();
};
bool grpc_tls_certificate_distributor::HasKeyCertPairs(
const std::string& identity_cert_name) {
grpc_core::MutexLock lock(&mu_);
const auto it = certificate_info_map_.find(identity_cert_name);
return it != certificate_info_map_.end() &&
!it->second.pem_key_cert_pairs.empty();
};
void grpc_tls_certificate_distributor::SetErrorForCert(
const std::string& cert_name, absl::optional<grpc_error*> root_cert_error,
absl::optional<grpc_error*> identity_cert_error) {
GPR_ASSERT(root_cert_error.has_value() || identity_cert_error.has_value());
grpc_core::MutexLock lock(&mu_);
CertificateInfo& cert_info = certificate_info_map_[cert_name];
if (root_cert_error.has_value()) {
for (auto* watcher_ptr : cert_info.root_cert_watchers) {
GPR_ASSERT(watcher_ptr != nullptr);
const auto watcher_it = watchers_.find(watcher_ptr);
GPR_ASSERT(watcher_it != watchers_.end());
// identity_cert_error_to_report is the error of the identity cert this
// watcher is watching, if there is any.
grpc_error* identity_cert_error_to_report = GRPC_ERROR_NONE;
if (identity_cert_error.has_value() &&
watcher_it->second.identity_cert_name == cert_name) {
identity_cert_error_to_report = *identity_cert_error;
} else if (watcher_it->second.identity_cert_name.has_value()) {
auto& identity_cert_info =
certificate_info_map_[*watcher_it->second.identity_cert_name];
identity_cert_error_to_report = identity_cert_info.identity_cert_error;
}
watcher_ptr->OnError(GRPC_ERROR_REF(*root_cert_error),
GRPC_ERROR_REF(identity_cert_error_to_report));
}
cert_info.SetRootError(*root_cert_error);
}
if (identity_cert_error.has_value()) {
for (auto* watcher_ptr : cert_info.identity_cert_watchers) {
GPR_ASSERT(watcher_ptr != nullptr);
const auto watcher_it = watchers_.find(watcher_ptr);
GPR_ASSERT(watcher_it != watchers_.end());
// root_cert_error_to_report is the error of the root cert this watcher is
// watching, if there is any.
grpc_error* root_cert_error_to_report = GRPC_ERROR_NONE;
if (root_cert_error.has_value() &&
watcher_it->second.root_cert_name == cert_name) {
// In this case, We've already sent the error updates at the time when
// checking root_cert_error, so we will skip here.
continue;
} else if (watcher_it->second.root_cert_name.has_value()) {
auto& root_cert_info =
certificate_info_map_[*watcher_it->second.root_cert_name];
root_cert_error_to_report = root_cert_info.root_cert_error;
}
watcher_ptr->OnError(GRPC_ERROR_REF(root_cert_error_to_report),
GRPC_ERROR_REF(*identity_cert_error));
}
cert_info.SetIdentityError(*identity_cert_error);
}
};
void grpc_tls_certificate_distributor::SetError(grpc_error* error) {
GPR_ASSERT(error != GRPC_ERROR_NONE);
grpc_core::MutexLock lock(&mu_);
for (const auto& watcher : watchers_) {
const auto watcher_ptr = watcher.first;
GPR_ASSERT(watcher_ptr != nullptr);
const auto& watcher_info = watcher.second;
watcher_ptr->OnError(
watcher_info.root_cert_name.has_value() ? GRPC_ERROR_REF(error)
: GRPC_ERROR_NONE,
watcher_info.identity_cert_name.has_value() ? GRPC_ERROR_REF(error)
: GRPC_ERROR_NONE);
}
for (auto& cert_info_entry : certificate_info_map_) {
auto& cert_info = cert_info_entry.second;
cert_info.SetRootError(GRPC_ERROR_REF(error));
cert_info.SetIdentityError(GRPC_ERROR_REF(error));
}
GRPC_ERROR_UNREF(error);
};
void grpc_tls_certificate_distributor::WatchTlsCertificates(
std::unique_ptr<TlsCertificatesWatcherInterface> watcher,
absl::optional<std::string> root_cert_name,
absl::optional<std::string> identity_cert_name) {
bool start_watching_root_cert = false;
bool already_watching_identity_for_root_cert = false;
bool start_watching_identity_cert = false;
bool already_watching_root_for_identity_cert = false;
GPR_ASSERT(root_cert_name.has_value() || identity_cert_name.has_value());
TlsCertificatesWatcherInterface* watcher_ptr = watcher.get();
GPR_ASSERT(watcher_ptr != nullptr);
// Update watchers_ and certificate_info_map_.
{
grpc_core::MutexLock lock(&mu_);
const auto watcher_it = watchers_.find(watcher_ptr);
// The caller needs to cancel the watcher first if it wants to re-register
// the watcher.
GPR_ASSERT(watcher_it == watchers_.end());
watchers_[watcher_ptr] = {std::move(watcher), root_cert_name,
identity_cert_name};
absl::optional<absl::string_view> updated_root_certs;
absl::optional<PemKeyCertPairList> updated_identity_pairs;
grpc_error* root_error = GRPC_ERROR_NONE;
grpc_error* identity_error = GRPC_ERROR_NONE;
if (root_cert_name.has_value()) {
CertificateInfo& cert_info = certificate_info_map_[*root_cert_name];
start_watching_root_cert = cert_info.root_cert_watchers.empty();
already_watching_identity_for_root_cert =
!cert_info.identity_cert_watchers.empty();
cert_info.root_cert_watchers.insert(watcher_ptr);
root_error = GRPC_ERROR_REF(cert_info.root_cert_error);
// Empty credentials will be treated as no updates.
if (!cert_info.pem_root_certs.empty()) {
updated_root_certs = cert_info.pem_root_certs;
}
}
if (identity_cert_name.has_value()) {
CertificateInfo& cert_info = certificate_info_map_[*identity_cert_name];
start_watching_identity_cert = cert_info.identity_cert_watchers.empty();
already_watching_root_for_identity_cert =
!cert_info.root_cert_watchers.empty();
cert_info.identity_cert_watchers.insert(watcher_ptr);
identity_error = GRPC_ERROR_REF(cert_info.identity_cert_error);
// Empty credentials will be treated as no updates.
if (!cert_info.pem_key_cert_pairs.empty()) {
updated_identity_pairs = cert_info.pem_key_cert_pairs;
}
}
// Notify this watcher if the certs it is watching already had some
// contents. Note that an *_cert_error in cert_info only indicates error
// occurred while trying to fetch the latest cert, but the updated_*_certs
// should always be valid. So we will send the updates regardless of
// *_cert_error.
if (updated_root_certs.has_value() || updated_identity_pairs.has_value()) {
watcher_ptr->OnCertificatesChanged(updated_root_certs,
std::move(updated_identity_pairs));
}
// Notify this watcher if the certs it is watching already had some errors.
if (root_error != GRPC_ERROR_NONE || identity_error != GRPC_ERROR_NONE) {
watcher_ptr->OnError(GRPC_ERROR_REF(root_error),
GRPC_ERROR_REF(identity_error));
}
GRPC_ERROR_UNREF(root_error);
GRPC_ERROR_UNREF(identity_error);
}
// Invoke watch status callback if needed.
{
grpc_core::MutexLock lock(&callback_mu_);
if (watch_status_callback_ != nullptr) {
if (root_cert_name == identity_cert_name &&
(start_watching_root_cert || start_watching_identity_cert)) {
watch_status_callback_(*root_cert_name, start_watching_root_cert,
start_watching_identity_cert);
} else {
if (start_watching_root_cert) {
watch_status_callback_(*root_cert_name, true,
already_watching_identity_for_root_cert);
}
if (start_watching_identity_cert) {
watch_status_callback_(*identity_cert_name,
already_watching_root_for_identity_cert, true);
}
}
}
}
};
void grpc_tls_certificate_distributor::CancelTlsCertificatesWatch(
TlsCertificatesWatcherInterface* watcher) {
absl::optional<std::string> root_cert_name;
absl::optional<std::string> identity_cert_name;
bool stop_watching_root_cert = false;
bool already_watching_identity_for_root_cert = false;
bool stop_watching_identity_cert = false;
bool already_watching_root_for_identity_cert = false;
// Update watchers_ and certificate_info_map_.
{
grpc_core::MutexLock lock(&mu_);
auto it = watchers_.find(watcher);
if (it == watchers_.end()) return;
WatcherInfo& watcher_info = it->second;
root_cert_name = std::move(watcher_info.root_cert_name);
identity_cert_name = std::move(watcher_info.identity_cert_name);
watchers_.erase(it);
if (root_cert_name.has_value()) {
auto it = certificate_info_map_.find(*root_cert_name);
GPR_ASSERT(it != certificate_info_map_.end());
CertificateInfo& cert_info = it->second;
cert_info.root_cert_watchers.erase(watcher);
stop_watching_root_cert = cert_info.root_cert_watchers.empty();
already_watching_identity_for_root_cert =
!cert_info.identity_cert_watchers.empty();
if (stop_watching_root_cert && !already_watching_identity_for_root_cert) {
certificate_info_map_.erase(it);
}
}
if (identity_cert_name.has_value()) {
auto it = certificate_info_map_.find(*identity_cert_name);
GPR_ASSERT(it != certificate_info_map_.end());
CertificateInfo& cert_info = it->second;
cert_info.identity_cert_watchers.erase(watcher);
stop_watching_identity_cert = cert_info.identity_cert_watchers.empty();
already_watching_root_for_identity_cert =
!cert_info.root_cert_watchers.empty();
if (stop_watching_identity_cert &&
!already_watching_root_for_identity_cert) {
certificate_info_map_.erase(it);
}
}
}
// Invoke watch status callback if needed.
{
grpc_core::MutexLock lock(&callback_mu_);
if (watch_status_callback_ != nullptr) {
if (root_cert_name == identity_cert_name &&
(stop_watching_root_cert || stop_watching_identity_cert)) {
watch_status_callback_(*root_cert_name, !stop_watching_root_cert,
!stop_watching_identity_cert);
} else {
if (stop_watching_root_cert) {
watch_status_callback_(*root_cert_name, false,
already_watching_identity_for_root_cert);
}
if (stop_watching_identity_cert) {
watch_status_callback_(*identity_cert_name,
already_watching_root_for_identity_cert,
false);
}
}
}
}
};

@ -0,0 +1,214 @@
//
// Copyright 2020 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef GRPC_CORE_LIB_SECURITY_CREDENTIALS_TLS_GRPC_TLS_CERTIFICATE_DISTRIBUTOR_H
#define GRPC_CORE_LIB_SECURITY_CREDENTIALS_TLS_GRPC_TLS_CERTIFICATE_DISTRIBUTOR_H
#include <grpc/support/port_platform.h>
#include <grpc/grpc_security.h>
#include "absl/container/inlined_vector.h"
#include "absl/types/optional.h"
#include "src/core/lib/gprpp/ref_counted.h"
#include "src/core/lib/security/security_connector/ssl_utils.h"
// TLS certificate distributor.
struct grpc_tls_certificate_distributor
: public grpc_core::RefCounted<grpc_tls_certificate_distributor> {
public:
typedef absl::InlinedVector<grpc_core::PemKeyCertPair, 1> PemKeyCertPairList;
// Interface for watching TLS certificates update.
class TlsCertificatesWatcherInterface {
public:
virtual ~TlsCertificatesWatcherInterface() = default;
// Handles the delivery of the updated root and identity certificates.
// An absl::nullopt value indicates no corresponding contents for
// root_certs or key_cert_pairs. Note that we will send updates of the
// latest contents for both root and identity certificates, even when only
// one side of it got updated.
//
// @param root_certs the contents of the reloaded root certs.
// @param key_cert_pairs the contents of the reloaded identity key-cert
// pairs.
virtual void OnCertificatesChanged(
absl::optional<absl::string_view> root_certs,
absl::optional<PemKeyCertPairList> key_cert_pairs) = 0;
// Handles an error that occurs while attempting to fetch certificate data.
// Note that if a watcher sees an error, it simply means the Provider is
// having problems renewing new data. If the watcher has previously received
// several OnCertificatesChanged, all the data received from that function
// is valid.
// In that case, watcher might simply log the error. If the watcher hasn't
// received any OnCertificatesChanged before the error occurs, no valid
// data is available yet, and the watcher should either fail or "waiting"
// for the valid data in a non-blocking way.
//
// @param root_cert_error the error occurred while reloading root
// certificates.
// @param identity_cert_error the error occurred while reloading identity
// certificates.
virtual void OnError(grpc_error* root_cert_error,
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.
//
// @param cert_name The name of the certificates being updated.
// @param pem_root_certs The content of root certificates.
// @param pem_key_cert_pairs The content of identity key-cert pairs.
void SetKeyMaterials(const std::string& cert_name,
absl::optional<std::string> pem_root_certs,
absl::optional<PemKeyCertPairList> pem_key_cert_pairs);
bool HasRootCerts(const std::string& root_cert_name);
bool HasKeyCertPairs(const std::string& identity_cert_name);
// Propagates the error that the caller (e.g. Producer) encounters to all the
// watchers watching a particular certificate name.
//
// @param cert_name The watching cert name of the watchers that the caller
// wants to notify when encountering error.
// @param root_cert_error The error that the caller encounters when reloading
// root certs.
// @param identity_cert_error The error that the caller encounters when
// reloading identity certs.
void SetErrorForCert(const std::string& cert_name,
absl::optional<grpc_error*> root_cert_error,
absl::optional<grpc_error*> identity_cert_error);
// Propagates the error that the caller (e.g. Producer) encounters to all
// watchers.
//
// @param error The error that the caller encounters.
void SetError(grpc_error* error);
// Sets the TLS certificate watch status callback function. The
// grpc_tls_certificate_distributor will invoke this callback when a new
// certificate name is watched by a newly registered watcher, or when a
// certificate name is no longer watched by any watchers.
// Note that when the callback shows a cert is no longer being watched, the
// distributor will delete the corresponding certificate data from its cache,
// and clear the corresponding error, if there is any. This means that if the
// callback subsequently says the same cert is now being watched again, the
// provider must re-provide the credentials or re-invoke the errors to the
// distributor, to indicate a successful or failed reloading.
// @param callback The callback function being set by the caller, e.g the
// Producer. Note that this callback will be invoked for each certificate
// name.
//
// For the parameters in the callback function:
// string_value The name of the certificates being watched.
// bool_value_1 If the root certificates with the specific name are being
// watched. bool_value_2 If the identity certificates with the specific name
// are being watched.
void SetWatchStatusCallback(
std::function<void(std::string, bool, bool)> callback) {
grpc_core::MutexLock lock(&mu_);
watch_status_callback_ = callback;
};
// Registers a watcher. The caller may keep a raw pointer to the watcher,
// which may be used only for cancellation. (Because the caller does not own
// the watcher, the pointer must not be used for any other purpose.) At least
// one of root_cert_name and identity_cert_name must be specified.
//
// @param watcher The watcher being registered.
// @param root_cert_name The name of the root certificates that will be
// watched. If set to absl::nullopt, the root certificates won't be watched.
// @param identity_cert_name The name of the identity certificates that will
// be watched. If set to absl::nullopt, the identity certificates won't be
// watched.
void WatchTlsCertificates(
std::unique_ptr<TlsCertificatesWatcherInterface> watcher,
absl::optional<std::string> root_cert_name,
absl::optional<std::string> identity_cert_name);
// Cancels a watcher.
//
// @param watcher The watcher being cancelled.
void CancelTlsCertificatesWatch(TlsCertificatesWatcherInterface* watcher);
private:
// Contains the information about each watcher.
struct WatcherInfo {
std::unique_ptr<TlsCertificatesWatcherInterface> watcher;
absl::optional<std::string> root_cert_name;
absl::optional<std::string> identity_cert_name;
};
// CertificateInfo contains the credential contents and some additional
// watcher information.
// Note that having errors doesn't indicate the corresponding credentials are
// invalid. For example, if root_cert_error != nullptr but pem_root_certs has
// value, it simply means an error occurs while trying to fetch the latest
// root certs, while pem_root_certs still contains the valid old data.
struct CertificateInfo {
// The contents of the root certificates.
std::string pem_root_certs;
// The contents of the identity key-certificate pairs.
PemKeyCertPairList pem_key_cert_pairs;
// The root cert reloading error propagated by the caller.
grpc_error* root_cert_error = GRPC_ERROR_NONE;
// The identity cert reloading error propagated by the caller.
grpc_error* identity_cert_error = GRPC_ERROR_NONE;
// The set of watchers watching root certificates.
// This is mainly used for quickly looking up the affected watchers while
// performing a credential reloading.
std::set<TlsCertificatesWatcherInterface*> root_cert_watchers;
// The set of watchers watching identity certificates. This is mainly used
// for quickly looking up the affected watchers while performing a
// credential reloading.
std::set<TlsCertificatesWatcherInterface*> identity_cert_watchers;
~CertificateInfo() {
GRPC_ERROR_UNREF(root_cert_error);
GRPC_ERROR_UNREF(identity_cert_error);
}
void SetRootError(grpc_error* error) {
GRPC_ERROR_UNREF(root_cert_error);
root_cert_error = error;
}
void SetIdentityError(grpc_error* error) {
GRPC_ERROR_UNREF(identity_cert_error);
identity_cert_error = error;
}
};
grpc_core::Mutex mu_;
// We need a dedicated mutex for watch_status_callback_ for allowing
// callers(e.g. Producer) to directly set key materials in the callback
// functions.
grpc_core::Mutex callback_mu_;
// Stores information about each watcher.
std::map<TlsCertificatesWatcherInterface*, WatcherInfo> watchers_;
// The callback to notify the caller, e.g. the Producer, that the watch status
// is changed.
std::function<void(std::string, bool, bool)> watch_status_callback_;
// Stores the names of each certificate, and their corresponding credential
// contents as well as some additional watcher information.
std::map<std::string, CertificateInfo> certificate_info_map_;
};
#endif // GRPC_CORE_LIB_SECURITY_CREDENTIALS_TLS_GRPC_TLS_CERTIFICATE_DISTRIBUTOR_H

@ -174,6 +174,11 @@ class PemKeyCertPair {
return *this;
}
bool operator==(const PemKeyCertPair& other) const {
return std::strcmp(this->private_key(), other.private_key()) == 0 &&
std::strcmp(this->cert_chain(), other.cert_chain()) == 0;
}
char* private_key() const { return private_key_.get(); }
char* cert_chain() const { return cert_chain_.get(); }

@ -393,6 +393,7 @@ CORE_SOURCE_FILES = [
'src/core/lib/security/credentials/oauth2/oauth2_credentials.cc',
'src/core/lib/security/credentials/plugin/plugin_credentials.cc',
'src/core/lib/security/credentials/ssl/ssl_credentials.cc',
'src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc',
'src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc',
'src/core/lib/security/credentials/tls/tls_credentials.cc',
'src/core/lib/security/security_connector/alts/alts_security_connector.cc',

@ -313,3 +313,16 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
],
)
grpc_cc_test(
name = "grpc_tls_certificate_distributor_test",
srcs = ["grpc_tls_certificate_distributor_test.cc"],
external_deps = ["gtest"],
language = "C++",
deps = [
"//:gpr",
"//:grpc",
"//:grpc_secure",
"//test/core/util:grpc_test_util",
],
)

@ -0,0 +1,968 @@
//
// 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_distributor.h"
#include <gmock/gmock.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <gtest/gtest.h>
#include <deque>
#include <list>
#include <string>
#include <thread>
#include "src/core/lib/slice/slice_internal.h"
#include "test/core/util/test_config.h"
namespace testing {
constexpr const char* kCertName1 = "cert_1_name";
constexpr const char* kCertName2 = "cert_2_name";
constexpr const char* kRootCert1Name = "root_cert_1_name";
constexpr const char* kRootCert1Contents = "root_cert_1_contents";
constexpr const char* kRootCert2Name = "root_cert_2_name";
constexpr const char* kRootCert2Contents = "root_cert_2_contents";
constexpr const char* kIdentityCert1Name = "identity_cert_1_name";
constexpr const char* kIdentityCert1PrivateKey = "identity_private_key_1";
constexpr const char* kIdentityCert1Contents = "identity_cert_1_contents";
constexpr const char* kIdentityCert2Name = "identity_cert_2_name";
constexpr const char* kIdentityCert2PrivateKey = "identity_private_key_2";
constexpr const char* kIdentityCert2Contents = "identity_cert_2_contents";
constexpr const char* kErrorMessage = "error_message";
constexpr const char* kRootErrorMessage = "root_error_message";
constexpr const char* kIdentityErrorMessage = "identity_error_message";
class GrpcTlsCertificateDistributorTest : public ::testing::Test {
protected:
// Forward declaration.
class TlsCertificatesTestWatcher;
static grpc_tls_certificate_distributor::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<grpc_ssl_pem_key_cert_pair*>(
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_tls_certificate_distributor::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_tls_certificate_distributor::PemKeyCertPairList key_cert_pairs;
CredentialInfo(
std::string root,
grpc_tls_certificate_distributor::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<CredentialInfo> cert_update_queue;
std::deque<ErrorInfo> error_queue;
std::deque<CredentialInfo> GetCredentialQueue() {
// We move the data member value so the data member will be re-initiated
// with size 0, and ready for the next check.
return std::move(cert_update_queue);
}
std::deque<ErrorInfo> GetErrorQueue() {
// We move the data member value so the data member will be re-initiated
// with size 0, and ready for the next check.
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() { state_->watcher = nullptr; }
void OnCertificatesChanged(
absl::optional<absl::string_view> root_certs,
absl::optional<grpc_tls_certificate_distributor::PemKeyCertPairList>
key_cert_pairs) override {
std::string updated_root;
if (root_certs.has_value()) {
updated_root = std::string(*root_certs);
}
grpc_tls_certificate_distributor::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 {
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(grpc_core::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(grpc_core::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_;
};
// CallbackStatus contains the parameters when calling watch_status_callback_
// of the distributor. When a particular callback is invoked, we will push a
// CallbackStatus to a callback_queue_, and check in each test if the status
// updates are correct.
struct CallbackStatus {
std::string cert_name;
bool root_being_watched;
bool identity_being_watched;
CallbackStatus(std::string name, bool root_watched, bool identity_watched)
: cert_name(std::move(name)),
root_being_watched(root_watched),
identity_being_watched(identity_watched) {}
bool operator==(const CallbackStatus& other) const {
return cert_name == other.cert_name &&
root_being_watched == other.root_being_watched &&
identity_being_watched == other.identity_being_watched;
}
};
void SetUp() override {
distributor_.SetWatchStatusCallback([this](std::string cert_name,
bool root_being_watched,
bool identity_being_watched) {
callback_queue_.emplace_back(std::move(cert_name), root_being_watched,
identity_being_watched);
});
}
WatcherState* MakeWatcher(absl::optional<std::string> root_cert_name,
absl::optional<std::string> identity_cert_name) {
grpc_core::MutexLock lock(&mu_);
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<TlsCertificatesTestWatcher>(&watchers_.back());
distributor_.WatchTlsCertificates(std::move(watcher),
std::move(root_cert_name),
std::move(identity_cert_name));
return &watchers_.back();
}
void CancelWatch(WatcherState* state) {
grpc_core::MutexLock lock(&mu_);
distributor_.CancelTlsCertificatesWatch(state->watcher);
EXPECT_EQ(state->watcher, nullptr);
}
std::deque<CallbackStatus> GetCallbackQueue() {
// We move the data member value so the data member will be re-initiated
// with size 0, and ready for the next check.
return std::move(callback_queue_);
}
grpc_tls_certificate_distributor distributor_;
// Use a std::list<> here to avoid the address invalidation caused by internal
// reallocation of std::vector<>.
std::list<WatcherState> watchers_;
std::deque<CallbackStatus> callback_queue_;
// This is to make watchers_ and callback_queue_ thread-safe.
grpc_core::Mutex mu_;
};
TEST_F(GrpcTlsCertificateDistributorTest, BasicCredentialBehaviors) {
EXPECT_FALSE(distributor_.HasRootCerts(kRootCert1Name));
EXPECT_FALSE(distributor_.HasKeyCertPairs(kIdentityCert1Name));
// After setting the certificates to the corresponding cert names, the
// distributor should possess the corresponding certs.
distributor_.SetKeyMaterials(kRootCert1Name, kRootCert1Contents,
absl::nullopt);
EXPECT_TRUE(distributor_.HasRootCerts(kRootCert1Name));
distributor_.SetKeyMaterials(
kIdentityCert1Name, absl::nullopt,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
EXPECT_TRUE(distributor_.HasKeyCertPairs(kIdentityCert1Name));
// Querying a non-existing cert name should return false.
EXPECT_FALSE(distributor_.HasRootCerts(kRootCert2Name));
EXPECT_FALSE(distributor_.HasKeyCertPairs(kIdentityCert2Name));
}
TEST_F(GrpcTlsCertificateDistributorTest, UpdateCredentialsOnAnySide) {
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1);
EXPECT_THAT(GetCallbackQueue(),
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(
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(
kRootCert2Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents))));
// Set identity certs should trigger watcher's OnCertificatesChanged again.
distributor_.SetKeyMaterials(
kCertName1, absl::nullopt,
MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents));
EXPECT_THAT(
watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert2Contents,
MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents))));
CancelWatch(watcher_state_1);
}
TEST_F(GrpcTlsCertificateDistributorTest, SameIdentityNameDiffRootName) {
// Register watcher 1.
WatcherState* watcher_state_1 =
MakeWatcher(kRootCert1Name, kIdentityCert1Name);
EXPECT_THAT(
GetCallbackQueue(),
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(
kRootCert2Name, true, false)));
// Push credential updates to kRootCert1Name and check if the status works as
// expected.
distributor_.SetKeyMaterials(kRootCert1Name, kRootCert1Contents,
absl::nullopt);
// Check the updates are delivered to watcher 1.
EXPECT_THAT(watcher_state_1->GetCredentialQueue(),
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, {})));
// Push credential updates to kIdentityCert1Name and check if the status works
// as expected.
distributor_.SetKeyMaterials(
kIdentityCert1Name, absl::nullopt,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
// Check the updates are delivered to watcher 1 and watcher 2.
EXPECT_THAT(
watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents))));
EXPECT_THAT(
watcher_state_2->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert2Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents))));
// Cancel watcher 1.
CancelWatch(watcher_state_1);
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)));
}
TEST_F(GrpcTlsCertificateDistributorTest, SameRootNameDiffIdentityName) {
// Register watcher 1.
WatcherState* watcher_state_1 =
MakeWatcher(kRootCert1Name, kIdentityCert1Name);
EXPECT_THAT(
GetCallbackQueue(),
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(
kIdentityCert2Name, false, true)));
// Push credential updates to kRootCert1Name and check if the status works as
// expected.
distributor_.SetKeyMaterials(kRootCert1Name, kRootCert1Contents,
absl::nullopt);
// Check the updates are delivered to watcher 1.
EXPECT_THAT(watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(kRootCert1Contents, {})));
// Check the updates are delivered to watcher 2.
EXPECT_THAT(watcher_state_2->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(kRootCert1Contents, {})));
// Push credential updates to SetKeyMaterials.
distributor_.SetKeyMaterials(
kIdentityCert1Name, absl::nullopt,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
// Check the updates are delivered to watcher 1.
EXPECT_THAT(
watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents))));
// Push credential updates to kIdentityCert2Name.
distributor_.SetKeyMaterials(
kIdentityCert2Name, absl::nullopt,
MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents));
// Check the updates are delivered to watcher 2.
EXPECT_THAT(
watcher_state_2->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents))));
// Cancel watcher 1.
CancelWatch(watcher_state_1);
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)));
}
TEST_F(GrpcTlsCertificateDistributorTest,
AddAndCancelFirstWatcherForSameRootAndIdentityCertName) {
// 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)));
// Push credential updates to kCertName1 and check if the status works as
// expected.
distributor_.SetKeyMaterials(
kCertName1, kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
// Check the updates are delivered to watcher 1.
EXPECT_THAT(
watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents))));
// Cancel watcher 1.
CancelWatch(watcher_state_1);
EXPECT_THAT(GetCallbackQueue(),
testing::ElementsAre(CallbackStatus(kCertName1, false, false)));
}
TEST_F(GrpcTlsCertificateDistributorTest,
AddAndCancelFirstWatcherForIdentityCertNameWithRootBeingWatched) {
// 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)));
// 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)));
// Push credential updates to kCertName1 and check if the status works as
// expected.
distributor_.SetKeyMaterials(
kCertName1, kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
// Check the updates are delivered to watcher 1.
EXPECT_THAT(watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(kRootCert1Contents, {})));
// Check the updates are delivered to watcher 2.
EXPECT_THAT(watcher_state_2->GetCredentialQueue(),
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, {})));
// Check the updates are not delivered to watcher 2.
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());
// Check the updates are delivered to watcher 2.
EXPECT_THAT(watcher_state_2->GetCredentialQueue(),
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)));
// Cancel watcher 1.
CancelWatch(watcher_state_1);
EXPECT_THAT(GetCallbackQueue(),
testing::ElementsAre(CallbackStatus(kCertName1, false, false)));
}
TEST_F(GrpcTlsCertificateDistributorTest,
AddAndCancelFirstWatcherForRootCertNameWithIdentityBeingWatched) {
// 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)));
// 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)));
// Push credential updates to kCertName1 and check if the status works as
// expected.
distributor_.SetKeyMaterials(
kCertName1, kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
// Check the updates are delivered to watcher 1.
EXPECT_THAT(watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
"", MakeCertKeyPairs(kIdentityCert1PrivateKey,
kIdentityCert1Contents))));
// Check the updates are delivered to watcher 2.
EXPECT_THAT(watcher_state_2->GetCredentialQueue(),
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, {})));
// Check the updates are not delivered to watcher 1.
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());
// Check the updates are delivered to watcher 1.
EXPECT_THAT(watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
"", MakeCertKeyPairs(kIdentityCert2PrivateKey,
kIdentityCert2Contents))));
// Cancel watcher 2.
CancelWatch(watcher_state_2);
EXPECT_THAT(GetCallbackQueue(),
testing::ElementsAre(CallbackStatus(kCertName1, false, true)));
// Cancel watcher 1.
CancelWatch(watcher_state_1);
EXPECT_THAT(GetCallbackQueue(),
testing::ElementsAre(CallbackStatus(kCertName1, false, false)));
}
TEST_F(GrpcTlsCertificateDistributorTest,
RemoveAllWatchersForCertNameAndAddAgain) {
// Register watcher 1 and watcher 2 watching kCertName1 for root and identity
// certs.
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1);
EXPECT_THAT(GetCallbackQueue(),
testing::ElementsAre(CallbackStatus(kCertName1, true, true)));
WatcherState* watcher_state_2 = MakeWatcher(kCertName1, kCertName1);
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());
// Cancel watcher 1.
CancelWatch(watcher_state_1);
EXPECT_THAT(GetCallbackQueue(),
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)));
// Push credential updates to kCertName1.
distributor_.SetKeyMaterials(
kCertName1, kRootCert2Contents,
MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents));
// Check the updates are delivered to watcher 3.
EXPECT_THAT(
watcher_state_3->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert2Contents,
MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents))));
// Cancel watcher 3.
CancelWatch(watcher_state_3);
EXPECT_THAT(GetCallbackQueue(),
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)));
// Reset callback to nullptr.
distributor_.SetWatchStatusCallback(nullptr);
// Cancel watcher 1 shouldn't trigger any callback.
CancelWatch(watcher_state_1);
EXPECT_THAT(GetCallbackQueue(), testing::ElementsAre());
}
TEST_F(GrpcTlsCertificateDistributorTest, SetKeyMaterialsInCallback) {
distributor_.SetWatchStatusCallback([this](std::string cert_name,
bool root_being_watched,
bool identity_being_watched) {
distributor_.SetKeyMaterials(
cert_name, kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
});
auto verify_function = [this](std::string cert_name) {
WatcherState* watcher_state_1 = MakeWatcher(cert_name, cert_name);
// Check the updates are delivered to watcher 1.
EXPECT_THAT(
watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert1Contents, MakeCertKeyPairs(kIdentityCert1PrivateKey,
kIdentityCert1Contents))));
CancelWatch(watcher_state_1);
};
// Start 1000 threads that will register a watcher to a new cert name, verify
// the key materials being set, and then cancel the watcher, to make sure the
// lock mechanism in the distributor is safe.
std::vector<std::thread> threads;
threads.reserve(1000);
for (int i = 0; i < 1000; ++i) {
threads.emplace_back(verify_function, std::to_string(i));
}
for (auto& th : threads) {
th.join();
}
}
TEST_F(GrpcTlsCertificateDistributorTest, WatchACertInfoWithValidCredentials) {
// Push credential updates to kCertName1.
distributor_.SetKeyMaterials(
kCertName1, kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
// Push root credential updates to kCertName2.
distributor_.SetKeyMaterials(kRootCert2Name, kRootCert2Contents,
absl::nullopt);
// Push identity credential updates to kCertName2.
distributor_.SetKeyMaterials(
kIdentityCert2Name, absl::nullopt,
MakeCertKeyPairs(kIdentityCert2PrivateKey, kIdentityCert2Contents));
// Register watcher 1.
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1);
// watcher 1 should receive the credentials right away.
EXPECT_THAT(
watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents))));
CancelWatch(watcher_state_1);
// Register watcher 2.
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, {})));
// 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(
"", MakeCertKeyPairs(kIdentityCert2PrivateKey,
kIdentityCert2Contents))));
CancelWatch(watcher_state_2);
CancelWatch(watcher_state_3);
}
TEST_F(GrpcTlsCertificateDistributorTest,
SetErrorForCertForBothRootAndIdentity) {
// Register watcher 1.
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1);
// Calling SetErrorForCert on both cert names should only call one OnError
// on watcher 1.
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, kIdentityErrorMessage)));
// Calling SetErrorForCert on root cert name should call OnError
// on watcher 1 again.
distributor_.SetErrorForCert(
kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kErrorMessage),
absl::nullopt);
EXPECT_THAT(
watcher_state_1->GetErrorQueue(),
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)));
distributor_.CancelTlsCertificatesWatch(watcher_state_1->watcher);
EXPECT_EQ(watcher_state_1->watcher, nullptr);
}
TEST_F(GrpcTlsCertificateDistributorTest, SetErrorForCertForRootOrIdentity) {
// Register watcher 1.
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, absl::nullopt);
// Calling SetErrorForCert on root name should only call one OnError
// on watcher 1.
distributor_.SetErrorForCert(
kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage),
absl::nullopt);
EXPECT_THAT(watcher_state_1->GetErrorQueue(),
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());
// 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, "")));
CancelWatch(watcher_state_1);
// Register watcher 2.
WatcherState* watcher_state_2 = MakeWatcher(absl::nullopt, kCertName1);
// Calling SetErrorForCert on identity name should only call one OnError
// on watcher 2.
distributor_.SetErrorForCert(
kCertName1, absl::nullopt,
GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage));
EXPECT_THAT(watcher_state_2->GetErrorQueue(),
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());
// 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)));
CancelWatch(watcher_state_2);
}
TEST_F(GrpcTlsCertificateDistributorTest,
SetErrorForIdentityNameWithPreexistingErrorForRootName) {
// SetErrorForCert for kCertName1.
distributor_.SetErrorForCert(
kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage),
GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage));
// Register watcher 1 for kCertName1 as root and kCertName2 as identity.
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, "")));
// 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(
ErrorInfo(kRootErrorMessage, kIdentityErrorMessage)));
CancelWatch(watcher_state_1);
}
TEST_F(GrpcTlsCertificateDistributorTest,
SetErrorForCertForRootNameWithSameNameForIdentityErrored) {
// SetErrorForCert for kCertName1.
distributor_.SetErrorForCert(
kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage),
GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage));
// Register watcher 1 for kCertName2 as root and kCertName1 as identity.
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)));
// 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(
ErrorInfo(kRootErrorMessage, kIdentityErrorMessage)));
CancelWatch(watcher_state_1);
}
TEST_F(GrpcTlsCertificateDistributorTest,
SetErrorForIdentityNameWithoutErrorForRootName) {
// 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());
// 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)));
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());
// 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)));
CancelWatch(watcher_state_2);
}
TEST_F(GrpcTlsCertificateDistributorTest,
SetErrorForRootNameWithPreexistingErrorForIdentityName) {
WatcherState* watcher_state_1 = MakeWatcher(kCertName2, kCertName1);
// Should not trigger OnError.
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, "")));
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());
// 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, "")));
CancelWatch(watcher_state_2);
}
TEST_F(GrpcTlsCertificateDistributorTest,
CancelTheLastWatcherOnAnErroredCertInfo) {
// Register watcher 1.
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1);
// Calling SetErrorForCert on both cert names should only call one OnError
// on watcher 1.
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, 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());
CancelWatch(watcher_state_2);
}
TEST_F(GrpcTlsCertificateDistributorTest,
WatchErroredCertInfoWithValidCredentialData) {
// Push credential updates to kCertName1.
distributor_.SetKeyMaterials(
kCertName1, kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
// Calling SetErrorForCert on both cert names.
distributor_.SetErrorForCert(
kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage),
GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage));
// Register watcher 1.
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1);
// watcher 1 should receive both the old credentials and the error right away.
EXPECT_THAT(
watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents))));
EXPECT_THAT(watcher_state_1->GetErrorQueue(),
testing::ElementsAre(
ErrorInfo(kRootErrorMessage, kIdentityErrorMessage)));
CancelWatch(watcher_state_1);
}
TEST_F(GrpcTlsCertificateDistributorTest,
SetErrorForCertThenSuccessfulCredentialUpdates) {
// Calling SetErrorForCert on both cert names.
distributor_.SetErrorForCert(
kCertName1, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage),
GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage));
// Push credential updates to kCertName1.
distributor_.SetKeyMaterials(
kCertName1, kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents));
// Register watcher 1.
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1);
// watcher 1 should only receive credential updates without any error, because
// the previous error is wiped out by a successful update.
EXPECT_THAT(
watcher_state_1->GetCredentialQueue(),
testing::ElementsAre(CredentialInfo(
kRootCert1Contents,
MakeCertKeyPairs(kIdentityCert1PrivateKey, kIdentityCert1Contents))));
EXPECT_THAT(watcher_state_1->GetErrorQueue(), testing::ElementsAre());
CancelWatch(watcher_state_1);
}
TEST_F(GrpcTlsCertificateDistributorTest, WatchCertInfoThenInvokeSetError) {
// Register watcher 1.
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, kCertName1);
// Register watcher 2.
WatcherState* watcher_state_2 = MakeWatcher(kRootCert1Name, absl::nullopt);
// Register watcher 3.
WatcherState* watcher_state_3 =
MakeWatcher(absl::nullopt, kIdentityCert1Name);
distributor_.SetError(GRPC_ERROR_CREATE_FROM_STATIC_STRING(kErrorMessage));
EXPECT_THAT(watcher_state_1->GetErrorQueue(),
testing::ElementsAre(ErrorInfo(kErrorMessage, kErrorMessage)));
EXPECT_THAT(watcher_state_2->GetErrorQueue(),
testing::ElementsAre(ErrorInfo(kErrorMessage, "")));
EXPECT_THAT(watcher_state_3->GetErrorQueue(),
testing::ElementsAre(ErrorInfo("", kErrorMessage)));
CancelWatch(watcher_state_1);
CancelWatch(watcher_state_2);
CancelWatch(watcher_state_3);
}
TEST_F(GrpcTlsCertificateDistributorTest, WatchErroredCertInfoBySetError) {
// Register watcher 1 watching kCertName1 as root.
WatcherState* watcher_state_1 = MakeWatcher(kCertName1, absl::nullopt);
// Register watcher 2 watching kCertName2 as identity.
WatcherState* watcher_state_2 = MakeWatcher(absl::nullopt, kCertName2);
// Call SetError and then cancel all watchers.
distributor_.SetError(GRPC_ERROR_CREATE_FROM_STATIC_STRING(kErrorMessage));
CancelWatch(watcher_state_1);
CancelWatch(watcher_state_2);
// 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());
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());
CancelWatch(watcher_state_4);
}
TEST_F(GrpcTlsCertificateDistributorTest, SetErrorForCertInCallback) {
distributor_.SetWatchStatusCallback([this](std::string cert_name,
bool root_being_watched,
bool identity_being_watched) {
this->distributor_.SetErrorForCert(
cert_name, GRPC_ERROR_CREATE_FROM_STATIC_STRING(kRootErrorMessage),
GRPC_ERROR_CREATE_FROM_STATIC_STRING(kIdentityErrorMessage));
});
auto verify_function = [this](std::string cert_name) {
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(
ErrorInfo(kRootErrorMessage, kIdentityErrorMessage)));
CancelWatch(watcher_state_1);
};
// Start 1000 threads that will register a watcher to a new cert name, verify
// the key materials being set, and then cancel the watcher, to make sure the
// lock mechanism in the distributor is safe.
std::vector<std::thread> threads;
threads.reserve(1000);
for (int i = 0; i < 1000; ++i) {
threads.emplace_back(verify_function, std::to_string(i));
}
for (auto& th : threads) {
th.join();
}
}
} // namespace testing
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;
}

@ -1757,6 +1757,8 @@ src/core/lib/security/credentials/plugin/plugin_credentials.cc \
src/core/lib/security/credentials/plugin/plugin_credentials.h \
src/core/lib/security/credentials/ssl/ssl_credentials.cc \
src/core/lib/security/credentials/ssl/ssl_credentials.h \
src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc \
src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h \
src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc \
src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h \
src/core/lib/security/credentials/tls/tls_credentials.cc \

@ -1586,6 +1586,8 @@ src/core/lib/security/credentials/plugin/plugin_credentials.cc \
src/core/lib/security/credentials/plugin/plugin_credentials.h \
src/core/lib/security/credentials/ssl/ssl_credentials.cc \
src/core/lib/security/credentials/ssl/ssl_credentials.h \
src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc \
src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h \
src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc \
src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h \
src/core/lib/security/credentials/tls/tls_credentials.cc \

@ -4455,6 +4455,30 @@
],
"uses_polling": false
},
{
"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_distributor_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": true
},
{
"args": [],
"benchmark": false,

Loading…
Cancel
Save