config backup (#4935)

This commit is contained in:
Giorgi Guliashvili 2018-08-20 14:24:24 +01:00 committed by GitHub
parent 84698b3e84
commit 561fda3aa0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 171 additions and 4 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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;
}
}