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
|