LCOV - code coverage report
Current view: top level - source/common/secret - sds_api.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 0 164 0.0 %
Date: 2024-01-05 06:35:25 Functions: 0 19 0.0 %

          Line data    Source code
       1             : #include "source/common/secret/sds_api.h"
       2             : 
       3             : #include "envoy/config/core/v3/config_source.pb.h"
       4             : #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h"
       5             : #include "envoy/service/discovery/v3/discovery.pb.h"
       6             : 
       7             : #include "source/common/common/assert.h"
       8             : #include "source/common/config/api_version.h"
       9             : #include "source/common/protobuf/utility.h"
      10             : 
      11             : namespace Envoy {
      12             : namespace Secret {
      13             : 
      14           0 : SdsApiStats SdsApi::generateStats(Stats::Scope& scope) {
      15           0 :   return {ALL_SDS_API_STATS(POOL_COUNTER(scope))};
      16           0 : }
      17             : 
      18             : SdsApi::SdsApi(envoy::config::core::v3::ConfigSource sds_config, absl::string_view sds_config_name,
      19             :                Config::SubscriptionFactory& subscription_factory, TimeSource& time_source,
      20             :                ProtobufMessage::ValidationVisitor& validation_visitor, Stats::Store& stats,
      21             :                std::function<void()> destructor_cb, Event::Dispatcher& dispatcher, Api::Api& api)
      22             :     : Envoy::Config::SubscriptionBase<envoy::extensions::transport_sockets::tls::v3::Secret>(
      23             :           validation_visitor, "name"),
      24           0 :       init_target_(fmt::format("SdsApi {}", sds_config_name), [this] { initialize(); }),
      25             :       dispatcher_(dispatcher), api_(api),
      26             :       scope_(stats.createScope(absl::StrCat("sds.", sds_config_name, "."))),
      27             :       sds_api_stats_(generateStats(*scope_)), sds_config_(std::move(sds_config)),
      28             :       sds_config_name_(sds_config_name), clean_up_(std::move(destructor_cb)),
      29             :       subscription_factory_(subscription_factory),
      30             :       time_source_(time_source), secret_data_{sds_config_name_, "uninitialized",
      31           0 :                                               time_source_.systemTime()} {
      32           0 :   const auto resource_name = getResourceName();
      33             :   // This has to happen here (rather than in initialize()) as it can throw exceptions.
      34           0 :   subscription_ = subscription_factory_.subscriptionFromConfigSource(
      35           0 :       sds_config_, Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_, {});
      36           0 : }
      37             : 
      38             : void SdsApi::resolveDataSource(const FileContentMap& files,
      39           0 :                                envoy::config::core::v3::DataSource& data_source) {
      40           0 :   if (data_source.specifier_case() ==
      41           0 :       envoy::config::core::v3::DataSource::SpecifierCase::kFilename) {
      42           0 :     const std::string& content = files.at(data_source.filename());
      43           0 :     data_source.set_inline_bytes(content);
      44           0 :   }
      45           0 : }
      46             : 
      47           0 : void SdsApi::onWatchUpdate() {
      48             :   // Filesystem reads and update callbacks can fail if the key material is missing or bad. We're not
      49             :   // under an onConfigUpdate() context, so we need to catch these cases explicitly here.
      50           0 :   TRY_ASSERT_MAIN_THREAD {
      51             :     // Obtain a stable set of files. If a rotation happens while we're reading,
      52             :     // then we need to try again.
      53           0 :     uint64_t prev_hash = 0;
      54           0 :     FileContentMap files = loadFiles();
      55           0 :     uint64_t next_hash = getHashForFiles(files);
      56           0 :     const uint64_t MaxBoundedRetries = 5;
      57           0 :     for (uint64_t bounded_retries = MaxBoundedRetries;
      58           0 :          next_hash != prev_hash && bounded_retries > 0; --bounded_retries) {
      59           0 :       files = loadFiles();
      60           0 :       prev_hash = next_hash;
      61           0 :       next_hash = getHashForFiles(files);
      62           0 :     }
      63           0 :     if (next_hash != prev_hash) {
      64           0 :       ENVOY_LOG_MISC(
      65           0 :           warn, "Unable to atomically refresh secrets due to > {} non-atomic rotations observed",
      66           0 :           MaxBoundedRetries);
      67           0 :     }
      68           0 :     const uint64_t new_hash = next_hash;
      69           0 :     if (new_hash != files_hash_) {
      70           0 :       resolveSecret(files);
      71           0 :       update_callback_manager_.runCallbacks();
      72           0 :       files_hash_ = new_hash;
      73           0 :     }
      74           0 :   }
      75           0 :   END_TRY
      76           0 :   CATCH(const EnvoyException& e, {
      77           0 :     ENVOY_LOG_MISC(warn, fmt::format("Failed to reload certificates: {}", e.what()));
      78           0 :     sds_api_stats_.key_rotation_failed_.inc();
      79           0 :   });
      80           0 : }
      81             : 
      82             : absl::Status SdsApi::onConfigUpdate(const std::vector<Config::DecodedResourceRef>& resources,
      83           0 :                                     const std::string& version_info) {
      84           0 :   const absl::Status status = validateUpdateSize(resources.size());
      85           0 :   if (!status.ok()) {
      86           0 :     return status;
      87           0 :   }
      88           0 :   const auto& secret = dynamic_cast<const envoy::extensions::transport_sockets::tls::v3::Secret&>(
      89           0 :       resources[0].get().resource());
      90             : 
      91           0 :   if (secret.name() != sds_config_name_) {
      92           0 :     return absl::InvalidArgumentError(
      93           0 :         fmt::format("Unexpected SDS secret (expecting {}): {}", sds_config_name_, secret.name()));
      94           0 :   }
      95             : 
      96           0 :   const uint64_t new_hash = MessageUtil::hash(secret);
      97             : 
      98           0 :   if (new_hash != secret_hash_) {
      99           0 :     validateConfig(secret);
     100           0 :     secret_hash_ = new_hash;
     101           0 :     setSecret(secret);
     102           0 :     const auto files = loadFiles();
     103           0 :     files_hash_ = getHashForFiles(files);
     104           0 :     resolveSecret(files);
     105           0 :     update_callback_manager_.runCallbacks();
     106             : 
     107           0 :     auto* watched_directory = getWatchedDirectory();
     108             :     // Either we have a watched path and can defer the watch monitoring to a
     109             :     // WatchedDirectory object, or we need to implement per-file watches in the else
     110             :     // clause.
     111           0 :     if (watched_directory != nullptr) {
     112           0 :       watched_directory->setCallback([this]() { onWatchUpdate(); });
     113           0 :     } else {
     114             :       // List DataSources that refer to files
     115           0 :       auto files = getDataSourceFilenames();
     116           0 :       if (!files.empty()) {
     117             :         // Create new watch, also destroys the old watch if any.
     118           0 :         watcher_ = dispatcher_.createFilesystemWatcher();
     119           0 :         for (auto const& filename : files) {
     120             :           // Watch for directory instead of file. This allows users to do atomic renames
     121             :           // on directory level (e.g. Kubernetes secret update).
     122           0 :           const auto result_or_error = api_.fileSystem().splitPathFromFilename(filename);
     123           0 :           THROW_IF_STATUS_NOT_OK(result_or_error, throw);
     124           0 :           watcher_->addWatch(absl::StrCat(result_or_error.value().directory_, "/"),
     125           0 :                              Filesystem::Watcher::Events::MovedTo,
     126           0 :                              [this](uint32_t) { onWatchUpdate(); });
     127           0 :         }
     128           0 :       } else {
     129           0 :         watcher_.reset(); // Destroy the old watch if any
     130           0 :       }
     131           0 :     }
     132           0 :   }
     133           0 :   secret_data_.last_updated_ = time_source_.systemTime();
     134           0 :   secret_data_.version_info_ = version_info;
     135           0 :   init_target_.ready();
     136           0 :   return absl::OkStatus();
     137           0 : }
     138             : 
     139             : absl::Status SdsApi::onConfigUpdate(const std::vector<Config::DecodedResourceRef>& added_resources,
     140             :                                     const Protobuf::RepeatedPtrField<std::string>&,
     141           0 :                                     const std::string&) {
     142           0 :   const absl::Status status = validateUpdateSize(added_resources.size());
     143           0 :   if (!status.ok()) {
     144           0 :     return status;
     145           0 :   }
     146           0 :   return onConfigUpdate(added_resources, added_resources[0].get().version());
     147           0 : }
     148             : 
     149             : void SdsApi::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason,
     150           0 :                                   const EnvoyException*) {
     151           0 :   ASSERT(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure != reason);
     152             :   // We need to allow server startup to continue, even if we have a bad config.
     153           0 :   init_target_.ready();
     154           0 : }
     155             : 
     156           0 : absl::Status SdsApi::validateUpdateSize(int num_resources) {
     157           0 :   if (num_resources == 0) {
     158           0 :     return absl::InvalidArgumentError(
     159           0 :         fmt::format("Missing SDS resources for {} in onConfigUpdate()", sds_config_name_));
     160           0 :   }
     161           0 :   if (num_resources != 1) {
     162           0 :     return absl::InvalidArgumentError(
     163           0 :         fmt::format("Unexpected SDS secrets length: {}", num_resources));
     164           0 :   }
     165           0 :   return absl::OkStatus();
     166           0 : }
     167             : 
     168           0 : void SdsApi::initialize() {
     169             :   // Don't put any code here that can throw exceptions, this has been the cause of multiple
     170             :   // hard-to-diagnose regressions.
     171           0 :   subscription_->start({sds_config_name_});
     172           0 : }
     173             : 
     174           0 : SdsApi::SecretData SdsApi::secretData() { return secret_data_; }
     175             : 
     176           0 : SdsApi::FileContentMap SdsApi::loadFiles() {
     177           0 :   FileContentMap files;
     178           0 :   for (auto const& filename : getDataSourceFilenames()) {
     179           0 :     auto file_or_error = api_.fileSystem().fileReadToEnd(filename);
     180           0 :     THROW_IF_STATUS_NOT_OK(file_or_error, throw);
     181           0 :     files[filename] = file_or_error.value();
     182           0 :   }
     183           0 :   return files;
     184           0 : }
     185             : 
     186           0 : uint64_t SdsApi::getHashForFiles(const FileContentMap& files) {
     187           0 :   uint64_t hash = 0;
     188           0 :   for (const auto& it : files) {
     189           0 :     hash = HashUtil::xxHash64(it.second, hash);
     190           0 :   }
     191           0 :   return hash;
     192           0 : }
     193             : 
     194           0 : std::vector<std::string> TlsCertificateSdsApi::getDataSourceFilenames() {
     195           0 :   std::vector<std::string> files;
     196           0 :   if (sds_tls_certificate_secrets_ && sds_tls_certificate_secrets_->has_certificate_chain() &&
     197           0 :       sds_tls_certificate_secrets_->certificate_chain().specifier_case() ==
     198           0 :           envoy::config::core::v3::DataSource::SpecifierCase::kFilename) {
     199           0 :     files.push_back(sds_tls_certificate_secrets_->certificate_chain().filename());
     200           0 :   }
     201           0 :   if (sds_tls_certificate_secrets_ && sds_tls_certificate_secrets_->has_private_key() &&
     202           0 :       sds_tls_certificate_secrets_->private_key().specifier_case() ==
     203           0 :           envoy::config::core::v3::DataSource::SpecifierCase::kFilename) {
     204           0 :     files.push_back(sds_tls_certificate_secrets_->private_key().filename());
     205           0 :   }
     206           0 :   return files;
     207           0 : }
     208             : 
     209           0 : std::vector<std::string> CertificateValidationContextSdsApi::getDataSourceFilenames() {
     210           0 :   std::vector<std::string> files;
     211           0 :   if (sds_certificate_validation_context_secrets_) {
     212           0 :     if (sds_certificate_validation_context_secrets_->has_trusted_ca() &&
     213           0 :         sds_certificate_validation_context_secrets_->trusted_ca().specifier_case() ==
     214           0 :             envoy::config::core::v3::DataSource::SpecifierCase::kFilename) {
     215           0 :       files.push_back(sds_certificate_validation_context_secrets_->trusted_ca().filename());
     216           0 :     }
     217           0 :     if (sds_certificate_validation_context_secrets_->has_crl() &&
     218           0 :         sds_certificate_validation_context_secrets_->crl().specifier_case() ==
     219           0 :             envoy::config::core::v3::DataSource::SpecifierCase::kFilename) {
     220           0 :       files.push_back(sds_certificate_validation_context_secrets_->crl().filename());
     221           0 :     }
     222           0 :   }
     223           0 :   return files;
     224           0 : }
     225             : 
     226           0 : std::vector<std::string> TlsSessionTicketKeysSdsApi::getDataSourceFilenames() { return {}; }
     227             : 
     228           0 : std::vector<std::string> GenericSecretSdsApi::getDataSourceFilenames() { return {}; }
     229             : 
     230             : } // namespace Secret
     231             : } // namespace Envoy

Generated by: LCOV version 1.15