mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-07 09:58:54 +00:00
config backup (#4935)
This commit is contained in:
parent
84698b3e84
commit
561fda3aa0
@ -15,11 +15,11 @@
|
||||
#include <vector>
|
||||
|
||||
#include <osquery/core.h>
|
||||
#include <osquery/core/json.h>
|
||||
#include <osquery/expected.h>
|
||||
#include <osquery/plugin.h>
|
||||
#include <osquery/query.h>
|
||||
|
||||
#include "osquery/core/json.h"
|
||||
|
||||
namespace osquery {
|
||||
|
||||
class Status;
|
||||
@ -44,12 +44,23 @@ class Config : private boost::noncopyable {
|
||||
private:
|
||||
Config();
|
||||
|
||||
void backupConfig(const std::map<std::string, std::string>& config);
|
||||
|
||||
public:
|
||||
~Config();
|
||||
|
||||
/// Singleton accessor.
|
||||
static Config& get();
|
||||
|
||||
enum class RestoreConfigError { DatabaseError = 1 };
|
||||
/**
|
||||
* @brief restoreConfigBackup retrieve backed up config
|
||||
* @return config persisted int the database
|
||||
*/
|
||||
static Expected<std::map<std::string, std::string>,
|
||||
Config::RestoreConfigError>
|
||||
restoreConfigBackup();
|
||||
|
||||
/**
|
||||
* @brief Update the internal config data.
|
||||
*
|
||||
@ -354,6 +365,8 @@ class Config : private boost::noncopyable {
|
||||
friend class FileEventsTableTests;
|
||||
friend class DecoratorsConfigParserPluginTests;
|
||||
friend class SchedulerTests;
|
||||
FRIEND_TEST(ConfigTests, test_config_backup);
|
||||
FRIEND_TEST(ConfigTests, test_config_backup_integrate);
|
||||
FRIEND_TEST(ConfigTests, test_config_refresh);
|
||||
FRIEND_TEST(ConfigTests, test_get_scheduled_queries);
|
||||
FRIEND_TEST(ConfigTests, test_nonblacklist_query);
|
||||
|
@ -35,6 +35,14 @@
|
||||
namespace rj = rapidjson;
|
||||
|
||||
namespace osquery {
|
||||
namespace {
|
||||
/// Prefix to persist config data
|
||||
const std::string kConfigPersistencePrefix{"config_persistence."};
|
||||
|
||||
using ConfigMap = std::map<std::string, std::string>;
|
||||
|
||||
std::atomic<bool> is_first_time_refresh(true);
|
||||
}; // namespace
|
||||
|
||||
/**
|
||||
* @brief Config plugin registry.
|
||||
@ -75,6 +83,12 @@ CLI_FLAG(uint64,
|
||||
config_accelerated_refresh,
|
||||
300,
|
||||
"Interval to wait if reading a configuration fails");
|
||||
|
||||
CLI_FLAG(bool,
|
||||
config_enable_backup,
|
||||
false,
|
||||
"Backup config and use it when refresh fails");
|
||||
|
||||
FLAG_ALIAS(google::uint64,
|
||||
config_tls_accelerated_refresh,
|
||||
config_accelerated_refresh);
|
||||
@ -98,6 +112,7 @@ std::atomic<size_t> kStartTime;
|
||||
// The config may be accessed and updated asynchronously; use mutexes.
|
||||
Mutex config_hash_mutex_;
|
||||
Mutex config_refresh_mutex_;
|
||||
Mutex config_backup_mutex_;
|
||||
|
||||
/// Several config methods require enumeration via predicate lambdas.
|
||||
RecursiveMutex config_schedule_mutex_;
|
||||
@ -455,6 +470,16 @@ Status Config::refresh() {
|
||||
}
|
||||
|
||||
loaded_ = true;
|
||||
|
||||
if (FLAGS_config_enable_backup && is_first_time_refresh.exchange(false)) {
|
||||
const auto result = restoreConfigBackup();
|
||||
if (!result) {
|
||||
return Status::failure(result.getError().getFullMessageRecursive());
|
||||
} else {
|
||||
update(*result);
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
} else if (getRefresh() != FLAGS_config_refresh) {
|
||||
VLOG(1) << "Normal configuration delay restored";
|
||||
@ -479,6 +504,7 @@ Status Config::refresh() {
|
||||
status = update(response[0]);
|
||||
}
|
||||
|
||||
is_first_time_refresh = false;
|
||||
loaded_ = true;
|
||||
return status;
|
||||
}
|
||||
@ -531,6 +557,50 @@ void stripConfigComments(std::string& json) {
|
||||
json = sink;
|
||||
}
|
||||
|
||||
Expected<ConfigMap, Config::RestoreConfigError> Config::restoreConfigBackup() {
|
||||
LOG(INFO) << "Restoring backed up config from the database";
|
||||
std::vector<std::string> keys;
|
||||
ConfigMap config;
|
||||
|
||||
WriteLock lock(config_backup_mutex_);
|
||||
scanDatabaseKeys(kPersistentSettings, keys, kConfigPersistencePrefix);
|
||||
|
||||
for (const auto& key : keys) {
|
||||
std::string value;
|
||||
Status status = getDatabaseValue(kPersistentSettings, key, value);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR)
|
||||
<< "restoreConfigBackup database failed to retrieve config for key "
|
||||
<< key;
|
||||
return createError(Config::RestoreConfigError::DatabaseError,
|
||||
"Could not retrieve value for the key: " + key);
|
||||
}
|
||||
config[key.substr(kConfigPersistencePrefix.length())] = std::move(value);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void Config::backupConfig(const ConfigMap& config) {
|
||||
LOG(INFO) << "BackupConfig started";
|
||||
std::vector<std::string> keys;
|
||||
|
||||
WriteLock lock(config_backup_mutex_);
|
||||
scanDatabaseKeys(kPersistentSettings, keys, kConfigPersistencePrefix);
|
||||
for (const auto& key : keys) {
|
||||
if (config.find(key.substr(kConfigPersistencePrefix.length())) ==
|
||||
config.end()) {
|
||||
deleteDatabaseValue(kPersistentSettings, key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& source : config) {
|
||||
setDatabaseValue(kPersistentSettings,
|
||||
kConfigPersistencePrefix + source.first,
|
||||
source.second);
|
||||
}
|
||||
}
|
||||
|
||||
Status Config::updateSource(const std::string& source,
|
||||
const std::string& json) {
|
||||
// Compute a 'synthesized' hash using the content before it is parsed.
|
||||
@ -660,7 +730,7 @@ void Config::applyParsers(const std::string& source,
|
||||
}
|
||||
}
|
||||
|
||||
Status Config::update(const std::map<std::string, std::string>& config) {
|
||||
Status Config::update(const ConfigMap& config) {
|
||||
// A config plugin may call update from an extension. This will update
|
||||
// the config instance within the extension process and the update must be
|
||||
// reflected in the core.
|
||||
@ -697,7 +767,8 @@ Status Config::update(const std::map<std::string, std::string>& config) {
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
// The content was not parsed correctly.
|
||||
LOG(ERROR) << "updateSource failed to parse config, of source: "
|
||||
<< source.first << " and content: " << source.second;
|
||||
return status;
|
||||
}
|
||||
// If a source was updated and the content has changed, then the registry
|
||||
@ -734,6 +805,10 @@ Status Config::update(const std::map<std::string, std::string>& config) {
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_config_enable_backup) {
|
||||
backupConfig(config);
|
||||
}
|
||||
|
||||
return Status(0, "OK");
|
||||
}
|
||||
|
||||
@ -801,6 +876,7 @@ void Config::reset() {
|
||||
std::map<std::string, std::string>().swap(hash_);
|
||||
valid_ = false;
|
||||
loaded_ = false;
|
||||
is_first_time_refresh = true;
|
||||
|
||||
refresh_runner_ = std::make_shared<ConfigRefreshRunner>();
|
||||
started_thread_ = false;
|
||||
|
@ -29,6 +29,7 @@ namespace osquery {
|
||||
|
||||
DECLARE_uint64(config_refresh);
|
||||
DECLARE_uint64(config_accelerated_refresh);
|
||||
DECLARE_bool(config_enable_backup);
|
||||
|
||||
const std::string kConfigTestNonBlacklistQuery{
|
||||
"pack_unrestricted_pack_process_heartbeat"};
|
||||
@ -108,6 +109,31 @@ class TestConfigPlugin : public ConfigPlugin {
|
||||
std::atomic<bool> fail_{false};
|
||||
};
|
||||
|
||||
class TestDataConfigParserPlugin : public ConfigParserPlugin {
|
||||
public:
|
||||
std::vector<std::string> keys() const override {
|
||||
return {"data"};
|
||||
}
|
||||
|
||||
Status setUp() override {
|
||||
return Status::success();
|
||||
}
|
||||
|
||||
Status update(const std::string& source,
|
||||
const ParserConfig& config) override {
|
||||
source_ = source;
|
||||
config_.clear();
|
||||
for (const auto& entry : config) {
|
||||
std::string content;
|
||||
entry.second.toString(content);
|
||||
config_[entry.first] = content;
|
||||
}
|
||||
return Status::success();
|
||||
}
|
||||
std::string source_{""};
|
||||
std::map<std::string, std::string> config_;
|
||||
};
|
||||
|
||||
TEST_F(ConfigTests, test_plugin) {
|
||||
auto& rf = RegistryFactory::get();
|
||||
auto plugin = std::make_shared<TestConfigPlugin>();
|
||||
@ -565,4 +591,56 @@ TEST_F(ConfigTests, test_config_refresh) {
|
||||
FLAGS_config_accelerated_refresh = refresh_acceleratred;
|
||||
rf.registry("config")->remove("test");
|
||||
}
|
||||
|
||||
TEST_F(ConfigTests, test_config_backup) {
|
||||
get().reset();
|
||||
const std::map<std::string, std::string> expected_config = {{"a", "b"},
|
||||
{"c", "d"}};
|
||||
get().backupConfig(expected_config);
|
||||
const auto config = get().restoreConfigBackup();
|
||||
EXPECT_TRUE(config);
|
||||
EXPECT_EQ(*config, expected_config);
|
||||
}
|
||||
|
||||
TEST_F(ConfigTests, test_config_backup_integrate) {
|
||||
const auto config_enable_backup_saved = FLAGS_config_enable_backup;
|
||||
FLAGS_config_enable_backup = true;
|
||||
|
||||
get().reset();
|
||||
auto& rf = RegistryFactory::get();
|
||||
auto data_parser = std::make_shared<TestDataConfigParserPlugin>();
|
||||
auto success_plugin = std::make_shared<TestConfigPlugin>();
|
||||
auto fail_plugin = std::make_shared<TestConfigPlugin>();
|
||||
fail_plugin->fail_ = true;
|
||||
success_plugin->fail_ = false;
|
||||
|
||||
rf.registry("config")->add("test_success", success_plugin);
|
||||
rf.registry("config")->add("test_fail", fail_plugin);
|
||||
rf.registry("config_parser")->add("test", data_parser);
|
||||
// Change the active config plugin.
|
||||
EXPECT_TRUE(rf.setActive("config", "test_success").ok());
|
||||
|
||||
auto status = get().refresh();
|
||||
EXPECT_TRUE(status.ok());
|
||||
EXPECT_EQ(success_plugin->gen_config_count_, 1);
|
||||
|
||||
auto source_backup = data_parser->source_;
|
||||
auto config_backup = data_parser->config_;
|
||||
|
||||
EXPECT_TRUE(source_backup.length() > 0);
|
||||
|
||||
get().reset();
|
||||
data_parser->source_.clear();
|
||||
data_parser->config_.clear();
|
||||
EXPECT_TRUE(rf.setActive("config", "test_fail").ok());
|
||||
|
||||
status = get().refresh();
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_EQ(fail_plugin->gen_config_count_, 1);
|
||||
|
||||
EXPECT_EQ(data_parser->source_, source_backup);
|
||||
EXPECT_EQ(data_parser->config_, config_backup);
|
||||
|
||||
FLAGS_config_enable_backup = config_enable_backup_saved;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user