/src/osquery/osquery/database/database.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * Copyright (c) 2014-present, The osquery authors |
3 | | * |
4 | | * This source code is licensed as defined by the LICENSE file found in the |
5 | | * root directory of this source tree. |
6 | | * |
7 | | * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) |
8 | | */ |
9 | | |
10 | | #include <boost/algorithm/string/predicate.hpp> |
11 | | #include <boost/io/quoted.hpp> |
12 | | #include <boost/property_tree/json_parser.hpp> |
13 | | |
14 | | #include <osquery/core/flagalias.h> |
15 | | #include <osquery/core/flags.h> |
16 | | #include <osquery/database/database.h> |
17 | | #include <osquery/logger/logger.h> |
18 | | #include <osquery/process/process.h> |
19 | | #include <osquery/registry/registry.h> |
20 | | #include <osquery/utils/config/default_paths.h> |
21 | | #include <osquery/utils/conversions/tryto.h> |
22 | | #include <osquery/utils/json/json.h> |
23 | | |
24 | | namespace pt = boost::property_tree; |
25 | | namespace rj = rapidjson; |
26 | | |
27 | | namespace osquery { |
28 | | /// Generate a specific-use registry for database access abstraction. |
29 | | CREATE_REGISTRY(DatabasePlugin, "database"); |
30 | | |
31 | | CLI_FLAG(bool, database_dump, false, "Dump the contents of the backing store"); |
32 | | |
33 | | CLI_FLAG(string, |
34 | | database_path, |
35 | | OSQUERY_DB_HOME "osquery.db", |
36 | | "If using a disk-based backing store, specify a path"); |
37 | | |
38 | | FLAG_ALIAS(std::string, db_path, database_path); |
39 | | |
40 | | FLAG(bool, disable_database, false, "Disable the persistent RocksDB storage"); |
41 | | |
42 | | const std::string kInternalDatabase = "rocksdb"; |
43 | | const std::string kPersistentSettings = "configurations"; |
44 | | const std::string kQueries = "queries"; |
45 | | const std::string kEvents = "events"; |
46 | | const std::string kCarves = "carves"; |
47 | | const std::string kLogs = "logs"; |
48 | | const std::string kDistributedQueries = "distributed"; |
49 | | const std::string kDistributedRunningQueries = "distributed_running"; |
50 | | const std::string kQueryPerformance = "query_performance"; |
51 | | |
52 | | const std::string kDbEpochSuffix = "epoch"; |
53 | | const std::string kDbCounterSuffix = "counter"; |
54 | | |
55 | | const std::string kDbVersionKey = "results_version"; |
56 | | |
57 | | const std::vector<std::string> kDomains = {kPersistentSettings, |
58 | | kQueries, |
59 | | kEvents, |
60 | | kLogs, |
61 | | kCarves, |
62 | | kDistributedQueries, |
63 | | kDistributedRunningQueries, |
64 | | kQueryPerformance}; |
65 | | |
66 | | std::atomic<bool> kDBAllowOpen(false); |
67 | | std::atomic<bool> kDBInitialized(false); |
68 | | std::atomic<bool> kDBChecking(false); |
69 | | |
70 | | /** |
71 | | * @brief A reader/writer mutex protecting database resets. |
72 | | * |
73 | | * A write is locked while using reset flows. A read is locked when calling |
74 | | * database plugin APIs. |
75 | | */ |
76 | | Mutex kDatabaseReset; |
77 | | |
78 | | /** |
79 | | * @brief Try multiple times to initialize persistent storage. |
80 | | * |
81 | | * It might be the case that other processes are stopping and have not released |
82 | | * their whole-process lock on the database. |
83 | | */ |
84 | | const size_t kDatabaseMaxRetryCount{25}; |
85 | | |
86 | | /// Number of milliseconds to pause between database initialize retries. |
87 | | const size_t kDatabaseRetryDelay{200}; |
88 | | |
89 | 1 | Status DatabasePlugin::reset() { |
90 | | // Keep this simple, scope the critical section to the broader methods. |
91 | 1 | tearDown(); |
92 | 1 | return setUp(); |
93 | 1 | } |
94 | | |
95 | 0 | bool DatabasePlugin::checkDB() { |
96 | 0 | kDBChecking = true; |
97 | 0 | bool result = true; |
98 | 0 | try { |
99 | 0 | auto status = setUp(); |
100 | 0 | tearDown(); |
101 | 0 | result = status.ok(); |
102 | 0 | } catch (const std::exception& e) { |
103 | 0 | VLOG(1) << "Database plugin check failed: " << e.what(); |
104 | 0 | result = false; |
105 | 0 | } |
106 | 0 | kDBChecking = false; |
107 | 0 | return result; |
108 | 0 | } |
109 | | |
110 | 0 | bool DatabasePlugin::allowOpen() const { |
111 | 0 | return kDBAllowOpen; |
112 | 0 | } |
113 | | |
114 | 0 | bool DatabasePlugin::checkingDB() const { |
115 | 0 | return kDBChecking; |
116 | 0 | } |
117 | | |
118 | | Status DatabasePlugin::scan(const std::string& domain, |
119 | | std::vector<std::string>& results, |
120 | | const std::string& prefix, |
121 | 0 | uint64_t max) const { |
122 | 0 | return Status::success(); |
123 | 0 | } |
124 | | |
125 | | Status DatabasePlugin::call(const PluginRequest& request, |
126 | 1 | PluginResponse& response) { |
127 | 1 | if (request.count("action") == 0) { |
128 | 0 | return Status(1, "Database plugin must include a request action"); |
129 | 0 | } |
130 | | |
131 | | // Get a domain/key, which are used for most database plugin actions. |
132 | 1 | auto domain = (request.count("domain") > 0) ? request.at("domain") : ""; |
133 | 1 | auto key = (request.count("key") > 0) ? request.at("key") : ""; |
134 | | |
135 | 1 | if (request.at("action") == "reset") { |
136 | 1 | WriteLock lock(kDatabaseReset); |
137 | 1 | kDBInitialized = false; |
138 | | // Prevent RocksDB reentrancy by logger plugins during plugin setup. |
139 | 1 | VLOG(1) << "Resetting the database plugin: " << getName(); |
140 | 1 | auto status = this->reset(); |
141 | 1 | if (!status.ok()) { |
142 | | // The active database could not be reset, fallback to an ephemeral. |
143 | 0 | Registry::get().setActive("database", "ephemeral"); |
144 | 0 | LOG(WARNING) << "Unable to reset database plugin: " << getName(); |
145 | 0 | } |
146 | 1 | kDBInitialized = true; |
147 | 1 | return status; |
148 | 1 | } |
149 | | |
150 | | // Switch over the possible database plugin actions. |
151 | 0 | ReadLock lock(kDatabaseReset); |
152 | 0 | if (request.at("action") == "get") { |
153 | 0 | std::string value; |
154 | 0 | auto status = this->get(domain, key, value); |
155 | 0 | response.push_back({{"v", value}}); |
156 | 0 | return status; |
157 | 0 | } else if (request.at("action") == "put") { |
158 | 0 | if (request.count("value") == 0) { |
159 | 0 | return Status(1, "Database plugin put action requires a value"); |
160 | 0 | } |
161 | 0 | return this->put(domain, key, request.at("value")); |
162 | 0 | } else if (request.at("action") == "putBatch") { |
163 | 0 | if (request.count("json") == 0) { |
164 | 0 | return Status( |
165 | 0 | 1, |
166 | 0 | "Database plugin putBatch action requires a json-encoded value list"); |
167 | 0 | } |
168 | | |
169 | 0 | auto json_object = JSON::newObject(); |
170 | |
|
171 | 0 | auto status = json_object.fromString(request.at("json")); |
172 | 0 | if (!status.ok()) { |
173 | 0 | VLOG(1) << status.getMessage(); |
174 | 0 | return status; |
175 | 0 | } |
176 | | |
177 | 0 | const auto& json_object_list = json_object.doc().GetObject(); |
178 | |
|
179 | 0 | DatabaseStringValueList data; |
180 | 0 | data.reserve(json_object_list.MemberCount()); |
181 | |
|
182 | 0 | for (auto& item : json_object_list) { |
183 | 0 | if (!item.value.IsString()) { |
184 | 0 | return Status(1, |
185 | 0 | "Database plugin putBatch action with an invalid json " |
186 | 0 | "received. Only string values are supported"); |
187 | 0 | } |
188 | | |
189 | 0 | data.push_back( |
190 | 0 | std::make_pair(item.name.GetString(), item.value.GetString())); |
191 | 0 | } |
192 | | |
193 | 0 | return this->putBatch(domain, data); |
194 | 0 | } else if (request.at("action") == "remove") { |
195 | 0 | return this->remove(domain, key); |
196 | 0 | } else if (request.at("action") == "remove_range") { |
197 | 0 | auto key_high = |
198 | 0 | (request.count("key_high") > 0) ? request.at("key_high") : ""; |
199 | 0 | if (!key_high.empty() && !key.empty()) { |
200 | 0 | return this->removeRange(domain, key, key_high); |
201 | 0 | } |
202 | 0 | return Status(1, "Missing range"); |
203 | 0 | } else if (request.at("action") == "scan") { |
204 | | // Accumulate scanned keys into a vector. |
205 | 0 | std::vector<std::string> keys; |
206 | | // Optionally allow the caller to request a max number of keys. |
207 | 0 | size_t max = 0; |
208 | 0 | if (request.count("max") > 0) { |
209 | 0 | max = std::stoul(request.at("max")); |
210 | 0 | } |
211 | 0 | auto status = this->scan(domain, keys, request.at("prefix"), max); |
212 | 0 | for (const auto& k : keys) { |
213 | 0 | response.push_back({{"k", k}}); |
214 | 0 | } |
215 | 0 | return status; |
216 | 0 | } |
217 | | |
218 | 0 | return Status(1, "Unknown database plugin action"); |
219 | 0 | } |
220 | | |
221 | 0 | static inline std::shared_ptr<DatabasePlugin> getDatabasePlugin() { |
222 | 0 | auto& rf = RegistryFactory::get(); |
223 | 0 | if (!rf.exists("database", rf.getActive("database"), true)) { |
224 | 0 | return nullptr; |
225 | 0 | } |
226 | | |
227 | 0 | auto plugin = rf.plugin("database", rf.getActive("database")); |
228 | 0 | return std::dynamic_pointer_cast<DatabasePlugin>(plugin); |
229 | 0 | } |
230 | | |
231 | | namespace { |
232 | | Status sendPutBatchDatabaseRequest(const std::string& domain, |
233 | 0 | const DatabaseStringValueList& data) { |
234 | 0 | auto json_object = JSON::newObject(); |
235 | 0 | for (const auto& p : data) { |
236 | 0 | const auto& key = p.first; |
237 | 0 | const auto& value = p.second; |
238 | |
|
239 | 0 | json_object.addRef(key, value); |
240 | 0 | } |
241 | |
|
242 | 0 | std::string serialized_data; |
243 | 0 | auto status = json_object.toString(serialized_data); |
244 | 0 | if (!status.ok()) { |
245 | 0 | VLOG(1) << status.getMessage(); |
246 | 0 | return status; |
247 | 0 | } |
248 | | |
249 | 0 | PluginRequest request = {{"action", "putBatch"}, |
250 | 0 | {"domain", domain}, |
251 | 0 | {"json", std::move(serialized_data)}}; |
252 | |
|
253 | 0 | status = Registry::call("database", request); |
254 | 0 | if (!status.ok()) { |
255 | 0 | VLOG(1) << status.getMessage(); |
256 | 0 | } |
257 | |
|
258 | 0 | return status; |
259 | 0 | } |
260 | | |
261 | | Status sendPutDatabaseRequest(const std::string& domain, |
262 | 0 | const DatabaseStringValueList& data) { |
263 | 0 | const auto& key = data[0].first; |
264 | 0 | const auto& value = data[0].second; |
265 | |
|
266 | 0 | PluginRequest request = { |
267 | 0 | {"action", "put"}, {"domain", domain}, {"key", key}, {"value", value}}; |
268 | |
|
269 | 0 | auto status = Registry::call("database", request); |
270 | 0 | if (!status.ok()) { |
271 | 0 | VLOG(1) << status.getMessage(); |
272 | 0 | } |
273 | |
|
274 | 0 | return status; |
275 | 0 | } |
276 | | } // namespace |
277 | | |
278 | | Status getDatabaseValue(const std::string& domain, |
279 | | const std::string& key, |
280 | 0 | std::string& value) { |
281 | 0 | if (domain.empty()) { |
282 | 0 | return Status(1, "Missing domain"); |
283 | 0 | } |
284 | | |
285 | 0 | if (RegistryFactory::get().external()) { |
286 | | // External registries (extensions) do not have databases active. |
287 | | // It is not possible to use an extension-based database. |
288 | 0 | PluginRequest request = { |
289 | 0 | {"action", "get"}, {"domain", domain}, {"key", key}}; |
290 | 0 | PluginResponse response; |
291 | 0 | auto status = Registry::call("database", request, response); |
292 | 0 | if (status.ok()) { |
293 | | // Set value from the internally-known "v" key. |
294 | 0 | if (response.size() > 0 && response[0].count("v") > 0) { |
295 | 0 | value = response[0].at("v"); |
296 | 0 | } |
297 | 0 | } |
298 | 0 | return status; |
299 | 0 | } |
300 | | |
301 | 0 | ReadLock lock(kDatabaseReset); |
302 | 0 | if (!kDBInitialized) { |
303 | 0 | throw std::runtime_error("Cannot get database value: " + key); |
304 | 0 | } else { |
305 | 0 | auto plugin = getDatabasePlugin(); |
306 | 0 | return plugin->get(domain, key, value); |
307 | 0 | } |
308 | 0 | } |
309 | | |
310 | | Status getDatabaseValue(const std::string& domain, |
311 | | const std::string& key, |
312 | 0 | int& value) { |
313 | 0 | std::string result; |
314 | 0 | auto s = getDatabaseValue(domain, key, result); |
315 | 0 | if (s.ok()) { |
316 | 0 | value = std::stoi(result); |
317 | 0 | } |
318 | 0 | return s; |
319 | 0 | } |
320 | | |
321 | | Status setDatabaseValue(const std::string& domain, |
322 | | const std::string& key, |
323 | 0 | const std::string& value) { |
324 | 0 | return setDatabaseBatch(domain, {std::make_pair(key, value)}); |
325 | 0 | } |
326 | | |
327 | | Status setDatabaseBatch(const std::string& domain, |
328 | 0 | const DatabaseStringValueList& data) { |
329 | 0 | if (domain.empty()) { |
330 | 0 | return Status(1, "Missing domain"); |
331 | 0 | } |
332 | | |
333 | | // External registries (extensions) do not have databases active. |
334 | | // It is not possible to use an extension-based database. |
335 | 0 | if (RegistryFactory::get().external()) { |
336 | 0 | if (data.size() > 1) { |
337 | 0 | return sendPutBatchDatabaseRequest(domain, data); |
338 | 0 | } else { |
339 | 0 | return sendPutDatabaseRequest(domain, data); |
340 | 0 | } |
341 | 0 | } |
342 | | |
343 | 0 | ReadLock lock(kDatabaseReset); |
344 | 0 | if (!kDBInitialized) { |
345 | 0 | throw std::runtime_error("Cannot set database values"); |
346 | 0 | } |
347 | | |
348 | 0 | auto plugin = getDatabasePlugin(); |
349 | 0 | return plugin->putBatch(domain, data); |
350 | 0 | } |
351 | | |
352 | | Status setDatabaseValue(const std::string& domain, |
353 | | const std::string& key, |
354 | 0 | int value) { |
355 | 0 | return setDatabaseBatch(domain, {std::make_pair(key, std::to_string(value))}); |
356 | 0 | } |
357 | | |
358 | 0 | Status deleteDatabaseValue(const std::string& domain, const std::string& key) { |
359 | 0 | if (domain.empty()) { |
360 | 0 | return Status(1, "Missing domain"); |
361 | 0 | } |
362 | | |
363 | 0 | if (RegistryFactory::get().external()) { |
364 | | // External registries (extensions) do not have databases active. |
365 | | // It is not possible to use an extension-based database. |
366 | 0 | PluginRequest request = { |
367 | 0 | {"action", "remove"}, {"domain", domain}, {"key", key}}; |
368 | 0 | return Registry::call("database", request); |
369 | 0 | } |
370 | | |
371 | 0 | ReadLock lock(kDatabaseReset); |
372 | 0 | if (!kDBInitialized) { |
373 | 0 | throw std::runtime_error("Cannot delete database value: " + key); |
374 | 0 | } else { |
375 | 0 | auto plugin = getDatabasePlugin(); |
376 | 0 | return plugin->remove(domain, key); |
377 | 0 | } |
378 | 0 | } |
379 | | |
380 | | Status deleteDatabaseRange(const std::string& domain, |
381 | | const std::string& low, |
382 | 0 | const std::string& high) { |
383 | 0 | if (domain.empty()) { |
384 | 0 | return Status(1, "Missing domain"); |
385 | 0 | } |
386 | | |
387 | 0 | if (RegistryFactory::get().external()) { |
388 | | // External registries (extensions) do not have databases active. |
389 | | // It is not possible to use an extension-based database. |
390 | 0 | PluginRequest request = {{"action", "remove_range"}, |
391 | 0 | {"domain", domain}, |
392 | 0 | {"key", low}, |
393 | 0 | {"key_high", high}}; |
394 | 0 | return Registry::call("database", request); |
395 | 0 | } |
396 | | |
397 | 0 | ReadLock lock(kDatabaseReset); |
398 | 0 | if (!kDBInitialized) { |
399 | 0 | throw std::runtime_error("Cannot delete database values: " + low + " - " + |
400 | 0 | high); |
401 | 0 | } else { |
402 | 0 | auto plugin = getDatabasePlugin(); |
403 | 0 | return plugin->removeRange(domain, low, high); |
404 | 0 | } |
405 | 0 | } |
406 | | |
407 | | Status scanDatabaseKeys(const std::string& domain, |
408 | | std::vector<std::string>& keys, |
409 | 0 | size_t max) { |
410 | 0 | return scanDatabaseKeys(domain, keys, "", max); |
411 | 0 | } |
412 | | |
413 | | /// Get a list of keys for a given domain. |
414 | | Status scanDatabaseKeys(const std::string& domain, |
415 | | std::vector<std::string>& keys, |
416 | | const std::string& prefix, |
417 | 0 | uint64_t max) { |
418 | 0 | if (domain.empty()) { |
419 | 0 | return Status(1, "Missing domain"); |
420 | 0 | } |
421 | | |
422 | 0 | if (RegistryFactory::get().external()) { |
423 | | // External registries (extensions) do not have databases active. |
424 | | // It is not possible to use an extension-based database. |
425 | 0 | PluginRequest request = {{"action", "scan"}, |
426 | 0 | {"domain", domain}, |
427 | 0 | {"prefix", prefix}, |
428 | 0 | {"max", std::to_string(max)}}; |
429 | 0 | PluginResponse response; |
430 | 0 | auto status = Registry::call("database", request, response); |
431 | |
|
432 | 0 | for (const auto& item : response) { |
433 | 0 | if (item.count("k") > 0) { |
434 | 0 | keys.push_back(item.at("k")); |
435 | 0 | } |
436 | 0 | } |
437 | 0 | return status; |
438 | 0 | } |
439 | | |
440 | 0 | ReadLock lock(kDatabaseReset); |
441 | 0 | if (!kDBInitialized) { |
442 | 0 | throw std::runtime_error("Cannot scan database values: " + prefix); |
443 | 0 | } else { |
444 | 0 | auto plugin = getDatabasePlugin(); |
445 | 0 | return plugin->scan(domain, keys, prefix, max); |
446 | 0 | } |
447 | 0 | } |
448 | | |
449 | 1 | void resetDatabase() { |
450 | 1 | PluginRequest request = {{"action", "reset"}}; |
451 | 1 | Registry::call("database", request); |
452 | 1 | } |
453 | | |
454 | 0 | void dumpDatabase() { |
455 | 0 | for (const auto& domain : kDomains) { |
456 | 0 | std::vector<std::string> keys; |
457 | 0 | if (!scanDatabaseKeys(domain, keys)) { |
458 | 0 | continue; |
459 | 0 | } |
460 | 0 | for (const auto& key : keys) { |
461 | 0 | std::string value; |
462 | 0 | if (!getDatabaseValue(domain, key, value)) { |
463 | 0 | continue; |
464 | 0 | } |
465 | 0 | fprintf( |
466 | 0 | stdout, "%s[%s]: %s\n", domain.c_str(), key.c_str(), value.c_str()); |
467 | 0 | } |
468 | 0 | } |
469 | 0 | fflush(stdout); |
470 | 0 | } |
471 | | |
472 | 1 | void setDatabaseAllowOpen(bool allow_open) { |
473 | 1 | kDBAllowOpen = allow_open; |
474 | 1 | } |
475 | | |
476 | 1 | Status initDatabasePlugin() { |
477 | 1 | if (kDBInitialized) { |
478 | 0 | return Status::success(); |
479 | 0 | } |
480 | | |
481 | | // Initialize the database plugin using the flag. |
482 | 1 | auto plugin = (FLAGS_disable_database) ? "ephemeral" : kInternalDatabase; |
483 | 1 | Status status; |
484 | 1 | for (size_t i = 0; i < kDatabaseMaxRetryCount; i++) { |
485 | 1 | status = RegistryFactory::get().setActive("database", plugin); |
486 | 1 | if (status.ok()) { |
487 | 1 | break; |
488 | 1 | } |
489 | | |
490 | 0 | if (FLAGS_disable_database) { |
491 | | // Do not try multiple times to initialize the ephemeral plugin. |
492 | 0 | break; |
493 | 0 | } |
494 | 0 | sleepFor(kDatabaseRetryDelay); |
495 | 0 | } |
496 | | |
497 | 1 | kDBInitialized = status.ok(); |
498 | 1 | return status; |
499 | 1 | } |
500 | | |
501 | 1 | Status initDatabasePluginForTesting() { |
502 | | // Use the built-in ephemeral database. |
503 | 1 | FLAGS_disable_database = true; |
504 | 1 | setDatabaseAllowOpen(); |
505 | 1 | initDatabasePlugin(); |
506 | 1 | resetDatabase(); |
507 | 1 | return Status::success(); |
508 | 1 | } |
509 | | |
510 | 0 | bool databaseInitialized() { |
511 | 0 | return kDBInitialized; |
512 | 0 | } |
513 | | |
514 | 0 | void shutdownDatabase() { |
515 | 0 | auto database_registry = RegistryFactory::get().registry("database"); |
516 | 0 | for (auto& plugin : RegistryFactory::get().names("database")) { |
517 | 0 | database_registry->remove(plugin); |
518 | 0 | } |
519 | 0 | } |
520 | | |
521 | 0 | Status ptreeToRapidJSON(const std::string& in, std::string& out) { |
522 | 0 | pt::ptree tree; |
523 | 0 | try { |
524 | 0 | std::stringstream ss; |
525 | 0 | ss << in; |
526 | 0 | pt::read_json(ss, tree); |
527 | 0 | } catch (const pt::json_parser::json_parser_error& /* e */) { |
528 | 0 | return Status(1, "Failed to parse JSON"); |
529 | 0 | } |
530 | | |
531 | 0 | auto json = JSON::newArray(); |
532 | 0 | for (const auto& t : tree) { |
533 | 0 | std::stringstream ss; |
534 | 0 | pt::write_json(ss, t.second); |
535 | |
|
536 | 0 | rj::Document row; |
537 | 0 | if (row.Parse(ss.str()).HasParseError()) { |
538 | 0 | return Status(1, "Failed to serialize JSON"); |
539 | 0 | } |
540 | 0 | json.push(row); |
541 | 0 | } |
542 | | |
543 | 0 | json.toString(out); |
544 | |
|
545 | 0 | return Status::success(); |
546 | 0 | } |
547 | | |
548 | 0 | static Status migrateV0V1(void) { |
549 | 0 | std::vector<std::string> keys; |
550 | 0 | auto s = scanDatabaseKeys(kQueries, keys); |
551 | 0 | if (!s.ok()) { |
552 | 0 | return Status(1, "Failed to lookup legacy query data from database"); |
553 | 0 | } |
554 | | |
555 | 0 | for (const auto& key : keys) { |
556 | | // Skip over epoch and counter entries, as 0 is parsed by ptree |
557 | 0 | if (boost::algorithm::ends_with(key, kDbEpochSuffix) || |
558 | 0 | boost::algorithm::ends_with(key, kDbCounterSuffix) || |
559 | 0 | boost::algorithm::starts_with(key, "query.")) { |
560 | 0 | continue; |
561 | 0 | } |
562 | | |
563 | 0 | std::string value{""}; |
564 | 0 | if (!getDatabaseValue(kQueries, key, value)) { |
565 | 0 | LOG(WARNING) << "Failed to get value from database " << key; |
566 | 0 | continue; |
567 | 0 | } |
568 | | |
569 | 0 | std::string out; |
570 | 0 | s = ptreeToRapidJSON(value, out); |
571 | 0 | if (!s.ok()) { |
572 | 0 | LOG(WARNING) << "Conversion from ptree to RapidJSON failed for '" << key |
573 | 0 | << ": " << value << "': " << s.what() << ". Dropping key!"; |
574 | 0 | continue; |
575 | 0 | } |
576 | | |
577 | 0 | if (!setDatabaseValue(kQueries, key, out)) { |
578 | 0 | LOG(WARNING) << "Failed to update value in database " << key << ": " |
579 | 0 | << value; |
580 | 0 | } |
581 | 0 | } |
582 | |
|
583 | 0 | return Status::success(); |
584 | 0 | } |
585 | | |
586 | 0 | static Status migrateV1V2(void) { |
587 | 0 | std::vector<std::string> keys; |
588 | 0 | const std::string audit_str(".audit."); |
589 | |
|
590 | 0 | Status s = scanDatabaseKeys(kEvents, keys); |
591 | 0 | if (!s.ok()) { |
592 | 0 | return Status::failure( |
593 | 0 | 1, "Failed to scan event keys from database: " + s.what()); |
594 | 0 | } |
595 | | |
596 | 0 | for (const auto& key : keys) { |
597 | 0 | const auto pos = key.find(audit_str); |
598 | 0 | if (pos != std::string::npos) { |
599 | 0 | std::string value; |
600 | 0 | std::string new_key = key; |
601 | 0 | new_key.replace(pos, audit_str.length(), ".auditeventpublisher."); |
602 | |
|
603 | 0 | s = getDatabaseValue(kEvents, key, value); |
604 | 0 | if (!s.ok()) { |
605 | 0 | LOG(ERROR) << "Failed to read value for key '" << key |
606 | 0 | << "'. Key will be kept but won't be migrated!"; |
607 | 0 | continue; |
608 | 0 | } |
609 | | |
610 | 0 | s = setDatabaseValue(kEvents, new_key, value); |
611 | 0 | if (!s.ok()) { |
612 | 0 | LOG(ERROR) << "Failed to set value for key '" << new_key |
613 | 0 | << "' migrated from '" << key |
614 | 0 | << "'. Original key will be kept but won't be migrated!"; |
615 | 0 | continue; |
616 | 0 | } |
617 | | |
618 | 0 | s = deleteDatabaseValue(kEvents, key); |
619 | 0 | if (!s.ok()) { |
620 | 0 | LOG(WARNING) << "Failed to delete key '" << key |
621 | 0 | << "' after migration to new key '" << new_key |
622 | 0 | << "'. Original key will be kept but data was migrated!"; |
623 | 0 | } |
624 | 0 | } |
625 | 0 | } |
626 | |
|
627 | 0 | return Status::success(); |
628 | 0 | } |
629 | | |
630 | 0 | Status upgradeDatabase(int to_version) { |
631 | 0 | std::string value; |
632 | 0 | Status st = getDatabaseValue(kPersistentSettings, kDbVersionKey, value); |
633 | |
|
634 | 0 | int db_version = 0; |
635 | | /* Since there isn't a reliable way to determined what happen when the read |
636 | | * fails we just assume the key doesn't exist which indicates database |
637 | | * version 0. |
638 | | */ |
639 | 0 | if (st.ok()) { |
640 | 0 | auto ret = tryTo<int>(value); |
641 | 0 | if (ret.isError()) { |
642 | 0 | LOG(ERROR) << "Invalid value '" << value << "'for " << kDbVersionKey |
643 | 0 | << " key. Database is corrupted."; |
644 | 0 | return Status::failure("Invalid value for database version."); |
645 | 0 | } else { |
646 | 0 | db_version = ret.get(); |
647 | 0 | } |
648 | 0 | } |
649 | | |
650 | 0 | while (db_version != to_version) { |
651 | 0 | Status migrate_status; |
652 | 0 | switch (db_version) { |
653 | 0 | case 0: |
654 | 0 | migrate_status = migrateV0V1(); |
655 | 0 | break; |
656 | | |
657 | 0 | case 1: |
658 | 0 | migrate_status = migrateV1V2(); |
659 | 0 | break; |
660 | | |
661 | 0 | default: |
662 | 0 | LOG(ERROR) << "Logic error: the migration code is broken!"; |
663 | 0 | migrate_status = Status::failure("Migration code broken."); |
664 | 0 | break; |
665 | 0 | } |
666 | | |
667 | 0 | if (!migrate_status.ok()) { |
668 | 0 | LOG(ERROR) << "Failed to migrate the database to version '" << db_version |
669 | 0 | << "': " << migrate_status.getMessage(); |
670 | 0 | return Status::failure("Database migration failed."); |
671 | 0 | } |
672 | | |
673 | 0 | st = setDatabaseValue( |
674 | 0 | kPersistentSettings, kDbVersionKey, std::to_string(db_version + 1)); |
675 | 0 | if (!st.ok()) { |
676 | 0 | LOG(ERROR) << "Failed to set new database version after migration. " |
677 | 0 | << "The DB was correctly migrated from version " << db_version |
678 | 0 | << " to version " << (db_version + 1) |
679 | 0 | << " but persisting the new version failed."; |
680 | 0 | return Status::failure("Database version commit failed."); |
681 | 0 | } |
682 | | |
683 | 0 | db_version++; |
684 | 0 | } |
685 | | |
686 | 0 | return Status::success(); |
687 | 0 | } |
688 | | |
689 | | class OsqueryDatabase final : public IDatabaseInterface { |
690 | | public: |
691 | | OsqueryDatabase() = default; |
692 | | virtual ~OsqueryDatabase() = default; |
693 | | |
694 | | virtual Status getDatabaseValue(const std::string& domain, |
695 | | const std::string& key, |
696 | 0 | std::string& value) const override { |
697 | 0 | return osquery::getDatabaseValue(domain, key, value); |
698 | 0 | } |
699 | | |
700 | | virtual Status getDatabaseValue(const std::string& domain, |
701 | | const std::string& key, |
702 | 0 | int& value) const override { |
703 | 0 | return osquery::getDatabaseValue(domain, key, value); |
704 | 0 | } |
705 | | |
706 | | virtual Status setDatabaseValue(const std::string& domain, |
707 | | const std::string& key, |
708 | 0 | const std::string& value) const override { |
709 | 0 | return osquery::setDatabaseValue(domain, key, value); |
710 | 0 | } |
711 | | |
712 | | virtual Status setDatabaseValue(const std::string& domain, |
713 | | const std::string& key, |
714 | 0 | int value) const override { |
715 | 0 | return osquery::setDatabaseValue(domain, key, value); |
716 | 0 | } |
717 | | |
718 | | virtual Status setDatabaseBatch( |
719 | | const std::string& domain, |
720 | 0 | const DatabaseStringValueList& data) const override { |
721 | 0 | return osquery::setDatabaseBatch(domain, data); |
722 | 0 | } |
723 | | |
724 | | virtual Status deleteDatabaseValue(const std::string& domain, |
725 | 0 | const std::string& key) const override { |
726 | 0 | return osquery::deleteDatabaseValue(domain, key); |
727 | 0 | } |
728 | | |
729 | | virtual Status deleteDatabaseRange(const std::string& domain, |
730 | | const std::string& low, |
731 | 0 | const std::string& high) const override { |
732 | 0 | return osquery::deleteDatabaseRange(domain, low, high); |
733 | 0 | } |
734 | | |
735 | | virtual Status scanDatabaseKeys(const std::string& domain, |
736 | | std::vector<std::string>& keys, |
737 | 0 | size_t max) const override { |
738 | 0 | return osquery::scanDatabaseKeys(domain, keys, max); |
739 | 0 | } |
740 | | |
741 | | virtual Status scanDatabaseKeys(const std::string& domain, |
742 | | std::vector<std::string>& keys, |
743 | | const std::string& prefix, |
744 | 0 | size_t max) const override { |
745 | 0 | return osquery::scanDatabaseKeys(domain, keys, prefix, max); |
746 | 0 | } |
747 | | }; |
748 | | |
749 | 0 | IDatabaseInterface& getOsqueryDatabase() { |
750 | 0 | static OsqueryDatabase osquery_database; |
751 | 0 | return osquery_database; |
752 | 0 | } |
753 | | } // namespace osquery |