2014-12-18 18:50:47 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2014, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
2015-01-21 20:56:25 +00:00
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
2014-12-18 18:50:47 +00:00
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*
|
|
|
|
*/
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2015-01-02 05:55:10 +00:00
|
|
|
#include <mutex>
|
2015-03-22 21:58:00 +00:00
|
|
|
#include <random>
|
2014-07-31 00:35:19 +00:00
|
|
|
#include <sstream>
|
|
|
|
|
2014-12-03 23:14:02 +00:00
|
|
|
#include <osquery/config.h>
|
|
|
|
#include <osquery/flags.h>
|
2015-01-16 01:40:42 +00:00
|
|
|
#include <osquery/hash.h>
|
2015-02-12 02:50:15 +00:00
|
|
|
#include <osquery/filesystem.h>
|
2015-01-21 21:36:55 +00:00
|
|
|
#include <osquery/logger.h>
|
2015-03-30 06:51:52 +00:00
|
|
|
#include <osquery/registry.h>
|
2014-12-11 01:35:21 +00:00
|
|
|
|
2014-07-31 00:35:19 +00:00
|
|
|
namespace pt = boost::property_tree;
|
2015-03-13 23:05:20 +00:00
|
|
|
|
|
|
|
typedef pt::ptree::value_type tree_node;
|
2015-02-12 02:50:15 +00:00
|
|
|
typedef std::map<std::string, std::vector<std::string> > EventFileMap_t;
|
|
|
|
|
2014-08-15 07:25:30 +00:00
|
|
|
namespace osquery {
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2015-03-03 23:03:14 +00:00
|
|
|
CLI_FLAG(string, config_plugin, "filesystem", "Config plugin name");
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
FLAG(int32, schedule_splay_percent, 10, "Percent to splay config times");
|
|
|
|
|
2015-01-02 05:55:10 +00:00
|
|
|
Status Config::load() {
|
2015-03-08 21:52:13 +00:00
|
|
|
auto& config_plugin = Registry::getActive("config");
|
|
|
|
if (!Registry::exists("config", config_plugin)) {
|
|
|
|
return Status(1, "Missing config plugin " + config_plugin);
|
2015-03-04 02:40:24 +00:00
|
|
|
}
|
|
|
|
|
2015-03-20 17:34:36 +00:00
|
|
|
return genConfig();
|
|
|
|
}
|
|
|
|
|
|
|
|
Status Config::update(const std::map<std::string, std::string>& config) {
|
2015-03-30 06:51:52 +00:00
|
|
|
// 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.
|
|
|
|
if (Registry::external()) {
|
|
|
|
for (const auto& source : config) {
|
|
|
|
PluginRequest request = {
|
2015-04-06 20:26:16 +00:00
|
|
|
{"action", "update"},
|
|
|
|
{"source", source.first},
|
|
|
|
{"data", source.second},
|
2015-03-30 06:51:52 +00:00
|
|
|
};
|
|
|
|
// A "update" registry item within core should call the core's update
|
|
|
|
// method. The config plugin call action handling must also know to
|
|
|
|
// update.
|
|
|
|
Registry::call("config", "update", request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-22 03:51:42 +00:00
|
|
|
// Request a unique write lock when updating config.
|
|
|
|
boost::unique_lock<boost::shared_mutex> unique_lock(getInstance().mutex_);
|
2015-01-02 05:55:10 +00:00
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
ConfigData conf;
|
2015-03-20 17:34:36 +00:00
|
|
|
for (const auto& source : config) {
|
2015-03-30 06:51:52 +00:00
|
|
|
if (Registry::external()) {
|
|
|
|
VLOG(1) << "Updating extension config source: " << source.first;
|
|
|
|
} else {
|
|
|
|
VLOG(1) << "Updating config source: " << source.first;
|
|
|
|
}
|
2015-03-20 17:34:36 +00:00
|
|
|
getInstance().raw_[source.first] = source.second;
|
2014-12-01 09:05:46 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
// Now merge all sources together.
|
|
|
|
for (const auto& source : getInstance().raw_) {
|
|
|
|
mergeConfig(source.second, conf);
|
2014-07-31 00:35:19 +00:00
|
|
|
}
|
2015-03-22 03:51:42 +00:00
|
|
|
|
2015-04-14 07:57:22 +00:00
|
|
|
// Call each parser with the optionally-empty, requested, top level keys.
|
|
|
|
for (const auto& plugin : Registry::all("config_parser")) {
|
|
|
|
auto parser = std::static_pointer_cast<ConfigParserPlugin>(plugin.second);
|
|
|
|
if (parser == nullptr || parser.get() == nullptr) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each key requested by the parser, add a property tree reference.
|
|
|
|
std::map<std::string, ConfigTree> parser_config;
|
|
|
|
for (const auto& key : parser->keys()) {
|
|
|
|
if (conf.all_data.count(key) > 0) {
|
|
|
|
parser_config[key] = conf.all_data.get_child(key);
|
|
|
|
} else {
|
|
|
|
parser_config[key] = pt::ptree();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
parser->update(parser_config);
|
|
|
|
}
|
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
getInstance().data_ = conf;
|
|
|
|
return Status(0, "OK");
|
2014-07-31 00:35:19 +00:00
|
|
|
}
|
|
|
|
|
2015-03-20 17:34:36 +00:00
|
|
|
Status Config::genConfig() {
|
2015-01-30 18:44:25 +00:00
|
|
|
PluginResponse response;
|
2015-03-08 21:52:13 +00:00
|
|
|
auto status = Registry::call("config", {{"action", "genConfig"}}, response);
|
2015-01-30 18:44:25 +00:00
|
|
|
if (!status.ok()) {
|
|
|
|
return status;
|
2014-07-31 00:35:19 +00:00
|
|
|
}
|
2015-01-04 07:12:28 +00:00
|
|
|
|
2015-03-13 23:05:20 +00:00
|
|
|
if (response.size() > 0) {
|
2015-03-20 17:34:36 +00:00
|
|
|
return update(response[0]);
|
2015-03-13 23:05:20 +00:00
|
|
|
}
|
2014-12-11 01:35:21 +00:00
|
|
|
return Status(0, "OK");
|
|
|
|
}
|
|
|
|
|
2015-03-22 03:51:42 +00:00
|
|
|
inline void mergeOption(const tree_node& option, ConfigData& conf) {
|
2015-04-24 08:44:41 +00:00
|
|
|
std::string key = option.first.data();
|
|
|
|
std::string value = option.second.data();
|
|
|
|
|
|
|
|
Flag::updateValue(key, value);
|
|
|
|
conf.options[key] = value;
|
2015-03-20 17:34:36 +00:00
|
|
|
if (conf.all_data.count("options") > 0) {
|
2015-04-24 08:44:41 +00:00
|
|
|
conf.all_data.get_child("options").erase(key);
|
2015-03-20 17:34:36 +00:00
|
|
|
}
|
2015-04-24 08:44:41 +00:00
|
|
|
conf.all_data.add_child("options." + key, option.second);
|
2015-03-13 23:05:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
// inline void mergeScheduledQuery(const tree_node& node, ConfigData& conf) {
|
|
|
|
inline void mergeScheduledQuery(const std::string& name,
|
|
|
|
const tree_node& node,
|
|
|
|
ConfigData& conf) {
|
2015-03-13 23:05:20 +00:00
|
|
|
// Read tree/JSON into a query structure.
|
2015-03-22 21:58:00 +00:00
|
|
|
ScheduledQuery query;
|
2015-03-13 23:05:20 +00:00
|
|
|
query.query = node.second.get<std::string>("query", "");
|
|
|
|
query.interval = node.second.get<int>("interval", 0);
|
2015-03-22 21:58:00 +00:00
|
|
|
|
|
|
|
// Check if this query exists, if so, check if it was changed.
|
|
|
|
if (conf.schedule.count(name) > 0) {
|
|
|
|
if (query == conf.schedule.at(name)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a new or updated scheduled query, update the splay.
|
|
|
|
query.splayed_interval =
|
|
|
|
splayValue(query.interval, FLAGS_schedule_splay_percent);
|
|
|
|
// Update the schedule map and replace the all_data node record.
|
|
|
|
conf.schedule[name] = query;
|
|
|
|
if (conf.all_data.count("schedule") > 0) {
|
|
|
|
conf.all_data.get_child("schedule").erase(name);
|
|
|
|
}
|
|
|
|
conf.all_data.add_child("schedule." + name, node.second);
|
2015-03-13 23:05:20 +00:00
|
|
|
}
|
|
|
|
|
2015-04-14 07:57:22 +00:00
|
|
|
inline void mergeExtraKey(const std::string& name,
|
|
|
|
const tree_node& node,
|
|
|
|
ConfigData& conf) {
|
|
|
|
// Automatically merge extra list/dict top level keys.
|
|
|
|
for (const auto& subitem : node.second) {
|
|
|
|
if (node.second.count("") == 0 && conf.all_data.count(name) > 0) {
|
|
|
|
conf.all_data.get_child(name).erase(subitem.first);
|
|
|
|
}
|
|
|
|
conf.all_data.add_child(name + "." + subitem.first, subitem.second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void mergeFilePath(const std::string& name,
|
|
|
|
const tree_node& node,
|
|
|
|
ConfigData& conf) {
|
|
|
|
for (const auto& path : node.second) {
|
|
|
|
resolveFilePattern(path.second.data(),
|
|
|
|
conf.files[node.first],
|
|
|
|
REC_LIST_FOLDERS | REC_EVENT_OPT);
|
|
|
|
}
|
|
|
|
conf.all_data.add_child(name + "." + node.first, node.second);
|
|
|
|
}
|
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
void Config::mergeConfig(const std::string& source, ConfigData& conf) {
|
|
|
|
std::stringstream json_data;
|
|
|
|
json_data << source;
|
|
|
|
|
|
|
|
pt::ptree tree;
|
2015-04-06 20:26:16 +00:00
|
|
|
try {
|
|
|
|
pt::read_json(json_data, tree);
|
|
|
|
} catch (const pt::ptree_error& e) {
|
2015-04-14 07:57:22 +00:00
|
|
|
VLOG(1) << "Error parsing config JSON: " << e.what();
|
2015-04-06 20:26:16 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-03-13 23:05:20 +00:00
|
|
|
|
2015-04-14 07:57:22 +00:00
|
|
|
if (tree.count("additional_monitoring") > 0) {
|
|
|
|
LOG(INFO) << RLOG(903) << "config 'additional_monitoring' is deprecated";
|
|
|
|
for (const auto& node : tree.get_child("additional_monitoring")) {
|
|
|
|
tree.add_child(node.first, node.second);
|
2015-03-22 21:58:00 +00:00
|
|
|
}
|
2015-04-14 07:57:22 +00:00
|
|
|
tree.erase("additional_monitoring");
|
2015-03-22 21:58:00 +00:00
|
|
|
}
|
2015-03-13 23:05:20 +00:00
|
|
|
|
2015-04-14 07:57:22 +00:00
|
|
|
for (const auto& item : tree) {
|
|
|
|
// Iterate over each top-level configuration key.
|
|
|
|
auto key = std::string(item.first.data());
|
|
|
|
if (key == "scheduledQueries") {
|
|
|
|
LOG(INFO) << RLOG(903) << "config 'scheduledQueries' is deprecated";
|
|
|
|
for (const auto& node : item.second) {
|
|
|
|
auto query_name = node.second.get<std::string>("name", "");
|
|
|
|
mergeScheduledQuery(query_name, node, conf);
|
|
|
|
}
|
|
|
|
} else if (key == "schedule") {
|
|
|
|
for (const auto& node : item.second) {
|
|
|
|
mergeScheduledQuery(node.first.data(), node, conf);
|
|
|
|
}
|
|
|
|
} else if (key == "options") {
|
|
|
|
for (const auto& option : item.second) {
|
|
|
|
mergeOption(option, conf);
|
|
|
|
}
|
|
|
|
} else if (key == "file_paths") {
|
|
|
|
for (const auto& category : item.second) {
|
|
|
|
mergeFilePath(key, category, conf);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mergeExtraKey(key, item, conf);
|
2014-12-01 09:05:46 +00:00
|
|
|
}
|
2015-03-22 21:58:00 +00:00
|
|
|
}
|
2015-04-14 07:57:22 +00:00
|
|
|
}
|
2015-02-10 22:32:54 +00:00
|
|
|
|
2015-04-14 07:57:22 +00:00
|
|
|
const pt::ptree& Config::getParsedData(const std::string& key) {
|
|
|
|
if (!Registry::exists("config_parser", key)) {
|
|
|
|
return getInstance().empty_data_;
|
2015-03-22 21:58:00 +00:00
|
|
|
}
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2015-04-14 07:57:22 +00:00
|
|
|
const auto& item = Registry::get("config_parser", key);
|
|
|
|
auto parser = std::static_pointer_cast<ConfigParserPlugin>(item);
|
|
|
|
if (parser == nullptr || parser.get() == nullptr) {
|
|
|
|
return getInstance().empty_data_;
|
2015-03-13 23:05:20 +00:00
|
|
|
}
|
2015-04-14 07:57:22 +00:00
|
|
|
|
|
|
|
return parser->data_;
|
2015-04-01 08:10:28 +00:00
|
|
|
}
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2015-04-17 20:03:43 +00:00
|
|
|
const ConfigPluginRef Config::getParser(const std::string& key) {
|
|
|
|
if (!Registry::exists("config_parser", key)) {
|
|
|
|
return ConfigPluginRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto& item = Registry::get("config_parser", key);
|
|
|
|
const auto parser = std::static_pointer_cast<ConfigParserPlugin>(item);
|
|
|
|
if (parser == nullptr || parser.get() == nullptr) {
|
|
|
|
return ConfigPluginRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
return parser;
|
|
|
|
}
|
|
|
|
|
2015-01-16 20:03:23 +00:00
|
|
|
Status Config::getMD5(std::string& hash_string) {
|
2015-03-22 03:51:42 +00:00
|
|
|
// Request an accessor to our own config, outside of an update.
|
|
|
|
ConfigDataInstance config;
|
|
|
|
|
2015-03-13 23:05:20 +00:00
|
|
|
std::stringstream out;
|
2015-04-14 07:57:22 +00:00
|
|
|
pt::write_json(out, config.data());
|
2014-12-11 01:35:21 +00:00
|
|
|
|
2015-01-20 22:05:01 +00:00
|
|
|
hash_string = osquery::hashFromBuffer(
|
2015-03-13 23:05:20 +00:00
|
|
|
HASH_TYPE_MD5, (void*)out.str().c_str(), out.str().length());
|
2014-12-11 01:35:21 +00:00
|
|
|
|
|
|
|
return Status(0, "OK");
|
|
|
|
}
|
2015-01-21 20:56:25 +00:00
|
|
|
|
2015-03-20 17:34:36 +00:00
|
|
|
Status Config::checkConfig() { return load(); }
|
2015-01-30 18:44:25 +00:00
|
|
|
|
|
|
|
Status ConfigPlugin::call(const PluginRequest& request,
|
|
|
|
PluginResponse& response) {
|
|
|
|
if (request.count("action") == 0) {
|
|
|
|
return Status(1, "Config plugins require an action in PluginRequest");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (request.at("action") == "genConfig") {
|
2015-03-13 23:05:20 +00:00
|
|
|
std::map<std::string, std::string> config;
|
|
|
|
auto stat = genConfig(config);
|
|
|
|
response.push_back(config);
|
|
|
|
return stat;
|
2015-03-25 03:59:28 +00:00
|
|
|
} else if (request.at("action") == "update") {
|
|
|
|
if (request.count("source") == 0 || request.count("data") == 0) {
|
|
|
|
return Status(1, "Missing source or data");
|
|
|
|
}
|
|
|
|
return Config::update({{request.at("source"), request.at("data")}});
|
2015-01-30 18:44:25 +00:00
|
|
|
}
|
|
|
|
return Status(1, "Config plugin action unknown: " + request.at("action"));
|
|
|
|
}
|
2015-03-22 21:58:00 +00:00
|
|
|
|
2015-04-14 07:57:22 +00:00
|
|
|
Status ConfigParserPlugin::setUp() {
|
|
|
|
for (const auto& key : keys()) {
|
|
|
|
data_.put(key, "");
|
|
|
|
}
|
|
|
|
return Status(0, "OK");
|
|
|
|
}
|
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
int splayValue(int original, int splayPercent) {
|
|
|
|
if (splayPercent <= 0 || splayPercent > 100) {
|
|
|
|
return original;
|
|
|
|
}
|
|
|
|
|
|
|
|
float percent_to_modify_by = (float)splayPercent / 100;
|
|
|
|
int possible_difference = original * percent_to_modify_by;
|
|
|
|
int max_value = original + possible_difference;
|
|
|
|
int min_value = original - possible_difference;
|
|
|
|
|
|
|
|
if (max_value == min_value) {
|
|
|
|
return max_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::default_random_engine generator;
|
|
|
|
std::uniform_int_distribution<int> distribution(min_value, max_value);
|
|
|
|
return distribution(generator);
|
|
|
|
}
|
2014-08-15 07:25:30 +00:00
|
|
|
}
|