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