blob: 9e69d986887368cc7042489ef27d6a796ff59e1e [file] [log] [blame]
//
// 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_provider.h"
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include "src/core/lib/gprpp/stat.h"
#include "src/core/lib/slice/slice_internal.h"
#include "src/core/lib/surface/api_trace.h"
namespace grpc_core {
StaticDataCertificateProvider::StaticDataCertificateProvider(
std::string root_certificate,
grpc_core::PemKeyCertPairList pem_key_cert_pairs)
: distributor_(MakeRefCounted<grpc_tls_certificate_distributor>()),
root_certificate_(std::move(root_certificate)),
pem_key_cert_pairs_(std::move(pem_key_cert_pairs)) {
distributor_->SetWatchStatusCallback([this](std::string cert_name,
bool root_being_watched,
bool identity_being_watched) {
grpc_core::MutexLock lock(&mu_);
absl::optional<std::string> root_certificate;
absl::optional<grpc_core::PemKeyCertPairList> pem_key_cert_pairs;
StaticDataCertificateProvider::WatcherInfo& info = watcher_info_[cert_name];
if (!info.root_being_watched && root_being_watched &&
!root_certificate_.empty()) {
root_certificate = root_certificate_;
}
info.root_being_watched = root_being_watched;
if (!info.identity_being_watched && identity_being_watched &&
!pem_key_cert_pairs_.empty()) {
pem_key_cert_pairs = pem_key_cert_pairs_;
}
info.identity_being_watched = identity_being_watched;
if (!info.root_being_watched && !info.identity_being_watched) {
watcher_info_.erase(cert_name);
}
const bool root_has_update = root_certificate.has_value();
const bool identity_has_update = pem_key_cert_pairs.has_value();
if (root_has_update || identity_has_update) {
distributor_->SetKeyMaterials(cert_name, std::move(root_certificate),
std::move(pem_key_cert_pairs));
}
grpc_error* root_cert_error = GRPC_ERROR_NONE;
grpc_error* identity_cert_error = GRPC_ERROR_NONE;
if (root_being_watched && !root_has_update) {
root_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Unable to get latest root certificates.");
}
if (identity_being_watched && !identity_has_update) {
identity_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Unable to get latest identity certificates.");
}
if (root_cert_error != GRPC_ERROR_NONE ||
identity_cert_error != GRPC_ERROR_NONE) {
distributor_->SetErrorForCert(cert_name, root_cert_error,
identity_cert_error);
}
});
}
StaticDataCertificateProvider::~StaticDataCertificateProvider() {
// Reset distributor's callback to make sure the callback won't be invoked
// again after this object(provider) is destroyed.
distributor_->SetWatchStatusCallback(nullptr);
}
namespace {
gpr_timespec TimeoutSecondsToDeadline(int64_t seconds) {
return gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
gpr_time_from_seconds(seconds, GPR_TIMESPAN));
}
} // namespace
FileWatcherCertificateProvider::FileWatcherCertificateProvider(
std::string private_key_path, std::string identity_certificate_path,
std::string root_cert_path, unsigned int refresh_interval_sec)
: private_key_path_(std::move(private_key_path)),
identity_certificate_path_(std::move(identity_certificate_path)),
root_cert_path_(std::move(root_cert_path)),
refresh_interval_sec_(refresh_interval_sec),
distributor_(MakeRefCounted<grpc_tls_certificate_distributor>()) {
// Private key and identity cert files must be both set or both unset.
GPR_ASSERT(private_key_path_.empty() == identity_certificate_path_.empty());
// Must be watching either root or identity certs.
GPR_ASSERT(!private_key_path_.empty() || !root_cert_path_.empty());
gpr_event_init(&shutdown_event_);
ForceUpdate();
auto thread_lambda = [](void* arg) {
FileWatcherCertificateProvider* provider =
static_cast<FileWatcherCertificateProvider*>(arg);
GPR_ASSERT(provider != nullptr);
while (true) {
void* value = gpr_event_wait(
&provider->shutdown_event_,
TimeoutSecondsToDeadline(provider->refresh_interval_sec_));
if (value != nullptr) {
return;
};
provider->ForceUpdate();
}
};
refresh_thread_ = grpc_core::Thread(
"FileWatcherCertificateProvider_refreshing_thread", thread_lambda, this);
refresh_thread_.Start();
distributor_->SetWatchStatusCallback([this](std::string cert_name,
bool root_being_watched,
bool identity_being_watched) {
grpc_core::MutexLock lock(&mu_);
absl::optional<std::string> root_certificate;
absl::optional<grpc_core::PemKeyCertPairList> pem_key_cert_pairs;
FileWatcherCertificateProvider::WatcherInfo& info =
watcher_info_[cert_name];
if (!info.root_being_watched && root_being_watched &&
!root_certificate_.empty()) {
root_certificate = root_certificate_;
}
info.root_being_watched = root_being_watched;
if (!info.identity_being_watched && identity_being_watched &&
!pem_key_cert_pairs_.empty()) {
pem_key_cert_pairs = pem_key_cert_pairs_;
}
info.identity_being_watched = identity_being_watched;
if (!info.root_being_watched && !info.identity_being_watched) {
watcher_info_.erase(cert_name);
}
ExecCtx exec_ctx;
if (root_certificate.has_value() || pem_key_cert_pairs.has_value()) {
distributor_->SetKeyMaterials(cert_name, root_certificate,
pem_key_cert_pairs);
}
grpc_error* root_cert_error = GRPC_ERROR_NONE;
grpc_error* identity_cert_error = GRPC_ERROR_NONE;
if (root_being_watched && !root_certificate.has_value()) {
root_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Unable to get latest root certificates.");
}
if (identity_being_watched && !pem_key_cert_pairs.has_value()) {
identity_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Unable to get latest identity certificates.");
}
if (root_cert_error != GRPC_ERROR_NONE ||
identity_cert_error != GRPC_ERROR_NONE) {
distributor_->SetErrorForCert(cert_name, root_cert_error,
identity_cert_error);
}
});
}
FileWatcherCertificateProvider::~FileWatcherCertificateProvider() {
// Reset distributor's callback to make sure the callback won't be invoked
// again after this object(provider) is destroyed.
distributor_->SetWatchStatusCallback(nullptr);
gpr_event_set(&shutdown_event_, (void*)(1));
refresh_thread_.Join();
}
void FileWatcherCertificateProvider::ForceUpdate() {
absl::optional<std::string> root_certificate;
absl::optional<grpc_core::PemKeyCertPairList> pem_key_cert_pairs;
if (!root_cert_path_.empty()) {
root_certificate = ReadRootCertificatesFromFile(root_cert_path_);
}
if (!private_key_path_.empty()) {
pem_key_cert_pairs = ReadIdentityKeyCertPairFromFiles(
private_key_path_, identity_certificate_path_);
}
grpc_core::MutexLock lock(&mu_);
const bool root_cert_changed =
(!root_certificate.has_value() && !root_certificate_.empty()) ||
(root_certificate.has_value() && root_certificate_ != *root_certificate);
if (root_cert_changed) {
if (root_certificate.has_value()) {
root_certificate_ = std::move(*root_certificate);
} else {
root_certificate_ = "";
}
}
const bool identity_cert_changed =
(!pem_key_cert_pairs.has_value() && !pem_key_cert_pairs_.empty()) ||
(pem_key_cert_pairs.has_value() &&
pem_key_cert_pairs_ != *pem_key_cert_pairs);
if (identity_cert_changed) {
if (pem_key_cert_pairs.has_value()) {
pem_key_cert_pairs_ = std::move(*pem_key_cert_pairs);
} else {
pem_key_cert_pairs_ = {};
}
}
if (root_cert_changed || identity_cert_changed) {
ExecCtx exec_ctx;
grpc_error* root_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Unable to get latest root certificates.");
grpc_error* identity_cert_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Unable to get latest identity certificates.");
for (const auto& p : watcher_info_) {
const std::string& cert_name = p.first;
const WatcherInfo& info = p.second;
absl::optional<std::string> root_to_report;
absl::optional<grpc_core::PemKeyCertPairList> identity_to_report;
// Set key materials to the distributor if their contents changed.
if (info.root_being_watched && !root_certificate_.empty() &&
root_cert_changed) {
root_to_report = root_certificate_;
}
if (info.identity_being_watched && !pem_key_cert_pairs_.empty() &&
identity_cert_changed) {
identity_to_report = pem_key_cert_pairs_;
}
if (root_to_report.has_value() || identity_to_report.has_value()) {
distributor_->SetKeyMaterials(cert_name, std::move(root_to_report),
std::move(identity_to_report));
}
// Report errors to the distributor if the contents are empty.
const bool report_root_error =
info.root_being_watched && root_certificate_.empty();
const bool report_identity_error =
info.identity_being_watched && pem_key_cert_pairs_.empty();
if (report_root_error || report_identity_error) {
distributor_->SetErrorForCert(
cert_name,
report_root_error ? GRPC_ERROR_REF(root_cert_error)
: GRPC_ERROR_NONE,
report_identity_error ? GRPC_ERROR_REF(identity_cert_error)
: GRPC_ERROR_NONE);
}
}
GRPC_ERROR_UNREF(root_cert_error);
GRPC_ERROR_UNREF(identity_cert_error);
}
}
absl::optional<std::string>
FileWatcherCertificateProvider::ReadRootCertificatesFromFile(
const std::string& root_cert_full_path) {
// Read the root file.
grpc_slice root_slice = grpc_empty_slice();
grpc_error* root_error =
grpc_load_file(root_cert_full_path.c_str(), 0, &root_slice);
if (root_error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "Reading file %s failed: %s",
root_cert_full_path.c_str(), grpc_error_string(root_error));
GRPC_ERROR_UNREF(root_error);
return absl::nullopt;
}
std::string root_cert(StringViewFromSlice(root_slice));
grpc_slice_unref_internal(root_slice);
return root_cert;
}
namespace {
// This helper function gets the last-modified time of |filename|. When failed,
// it logs the error and returns 0.
time_t GetModificationTime(const char* filename) {
time_t ts = 0;
absl::Status status = grpc_core::GetFileModificationTime(filename, &ts);
return ts;
}
} // namespace
absl::optional<PemKeyCertPairList>
FileWatcherCertificateProvider::ReadIdentityKeyCertPairFromFiles(
const std::string& private_key_path,
const std::string& identity_certificate_path) {
struct SliceWrapper {
grpc_slice slice = grpc_empty_slice();
~SliceWrapper() { grpc_slice_unref_internal(slice); }
};
const int kNumRetryAttempts = 3;
for (int i = 0; i < kNumRetryAttempts; ++i) {
// TODO(ZhenLian): replace the timestamp approach with key-match approach
// once the latter is implemented.
// Checking the last modification of identity files before reading.
time_t identity_key_ts_before =
GetModificationTime(private_key_path.c_str());
if (identity_key_ts_before == 0) {
gpr_log(
GPR_ERROR,
"Failed to get the file's modification time of %s. Start retrying...",
private_key_path.c_str());
continue;
}
time_t identity_cert_ts_before =
GetModificationTime(identity_certificate_path.c_str());
if (identity_cert_ts_before == 0) {
gpr_log(
GPR_ERROR,
"Failed to get the file's modification time of %s. Start retrying...",
identity_certificate_path.c_str());
continue;
}
// Read the identity files.
SliceWrapper key_slice, cert_slice;
grpc_error* key_error =
grpc_load_file(private_key_path.c_str(), 0, &key_slice.slice);
if (key_error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "Reading file %s failed: %s. Start retrying...",
private_key_path.c_str(), grpc_error_string(key_error));
GRPC_ERROR_UNREF(key_error);
continue;
}
grpc_error* cert_error =
grpc_load_file(identity_certificate_path.c_str(), 0, &cert_slice.slice);
if (cert_error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "Reading file %s failed: %s. Start retrying...",
identity_certificate_path.c_str(), grpc_error_string(cert_error));
GRPC_ERROR_UNREF(cert_error);
continue;
}
std::string private_key(StringViewFromSlice(key_slice.slice));
std::string cert_chain(StringViewFromSlice(cert_slice.slice));
grpc_ssl_pem_key_cert_pair* ssl_pair =
static_cast<grpc_ssl_pem_key_cert_pair*>(
gpr_malloc(sizeof(grpc_ssl_pem_key_cert_pair)));
ssl_pair->private_key = gpr_strdup(private_key.c_str());
ssl_pair->cert_chain = gpr_strdup(cert_chain.c_str());
PemKeyCertPairList identity_pairs;
identity_pairs.emplace_back(ssl_pair);
// Checking the last modification of identity files before reading.
time_t identity_key_ts_after =
GetModificationTime(private_key_path.c_str());
if (identity_key_ts_before != identity_key_ts_after) {
gpr_log(GPR_ERROR,
"Last modified time before and after reading %s is not the same. "
"Start retrying...",
private_key_path.c_str());
continue;
}
time_t identity_cert_ts_after =
GetModificationTime(identity_certificate_path.c_str());
if (identity_cert_ts_before != identity_cert_ts_after) {
gpr_log(GPR_ERROR,
"Last modified time before and after reading %s is not the same. "
"Start retrying...",
identity_certificate_path.c_str());
continue;
}
return identity_pairs;
}
gpr_log(GPR_ERROR,
"All retry attempts failed. Will try again after the next interval.");
return absl::nullopt;
}
} // namespace grpc_core
/** -- Wrapper APIs declared in grpc_security.h -- **/
grpc_tls_certificate_provider* grpc_tls_certificate_provider_static_data_create(
const char* root_certificate, grpc_tls_identity_pairs* pem_key_cert_pairs) {
GPR_ASSERT(root_certificate != nullptr || pem_key_cert_pairs != nullptr);
grpc_core::PemKeyCertPairList identity_pairs_core;
if (pem_key_cert_pairs != nullptr) {
identity_pairs_core = std::move(pem_key_cert_pairs->pem_key_cert_pairs);
delete pem_key_cert_pairs;
}
std::string root_cert_core;
if (root_certificate != nullptr) {
root_cert_core = root_certificate;
}
return new grpc_core::StaticDataCertificateProvider(
std::move(root_cert_core), std::move(identity_pairs_core));
}
grpc_tls_certificate_provider*
grpc_tls_certificate_provider_file_watcher_create(
const char* private_key_path, const char* identity_certificate_path,
const char* root_cert_path, unsigned int refresh_interval_sec) {
return new grpc_core::FileWatcherCertificateProvider(
private_key_path == nullptr ? "" : private_key_path,
identity_certificate_path == nullptr ? "" : identity_certificate_path,
root_cert_path == nullptr ? "" : root_cert_path, refresh_interval_sec);
}
void grpc_tls_certificate_provider_release(
grpc_tls_certificate_provider* provider) {
GRPC_API_TRACE("grpc_tls_certificate_provider_release(provider=%p)", 1,
(provider));
grpc_core::ExecCtx exec_ctx;
if (provider != nullptr) provider->Unref();
}