mirror of https://github.com/grpc/grpc.git
parent
e6e6be4b0b
commit
d74e43da95
26 changed files with 1949 additions and 147 deletions
@ -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 <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 "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<CredentialInfo> cert_update_queue; |
||||
std::deque<ErrorInfo> error_queue; |
||||
Mutex mu; |
||||
|
||||
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.
|
||||
MutexLock lock(&mu); |
||||
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.
|
||||
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<absl::string_view> root_certs, |
||||
absl::optional<PemKeyCertPairList> 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<grpc_tls_certificate_distributor> distributor, |
||||
absl::optional<std::string> root_cert_name, |
||||
absl::optional<std::string> 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<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) { |
||||
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<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_; |
||||
// 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<TmpFile>(root_cert_); |
||||
auto tmp_identity_key = absl::make_unique<TmpFile>(private_key_); |
||||
auto tmp_identity_cert = absl::make_unique<TmpFile>(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<TmpFile>(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<TmpFile>(private_key_); |
||||
auto tmp_identity_cert = absl::make_unique<TmpFile>(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; |
||||
} |
@ -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<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); |
||||
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
|
@ -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
|
Loading…
Reference in new issue