TEST_F(ConfigTests, test_config_parser) { // Register a config parser plugin. Registry::add<TestConfigParserPlugin>("config_parser", "test"); Registry::get("config_parser", "test")->setUp(); { // Access the parser's data without having updated the configuration. ConfigDataInstance config; const auto& test_data = config.getParsedData("test"); // Expect the setUp method to have run and set blank defaults. // Accessing an invalid property tree key will abort. ASSERT_EQ(test_data.get_child("dictionary").count(""), 0); } // Update or load the config, expect the parser to be called. Config::update( {{"source1", "{\"dictionary\": {\"key1\": \"value1\"}, \"list\": [\"first\"]}"}}); ASSERT_TRUE(TestConfigParserPlugin::update_called); { // Now access the parser's data AFTER updating the config (no longer blank) ConfigDataInstance config; const auto& test_data = config.getParsedData("test"); // Expect a value that existed in the configuration. EXPECT_EQ(test_data.count("dictionary"), 1); EXPECT_EQ(test_data.get("dictionary.key1", ""), "value1"); // Expect a value for every key the parser requested. // Every requested key will be present, event if the key's tree is empty. EXPECT_EQ(test_data.count("dictionary2"), 1); // Expect the parser-created data item. EXPECT_EQ(test_data.count("dictionary3"), 1); EXPECT_EQ(test_data.get("dictionary3.key2", ""), "value2"); } // Update from a secondary source into a dictionary. // Expect that the keys in the top-level dictionary are merged. Config::update({{"source2", "{\"dictionary\": {\"key3\": \"value3\"}}"}}); // Update from a third source into a list. // Expect that the items from each source in the top-level list are merged. Config::update({{"source3", "{\"list\": [\"second\"]}"}}); { ConfigDataInstance config; const auto& test_data = config.getParsedData("test"); EXPECT_EQ(test_data.count("dictionary"), 1); EXPECT_EQ(test_data.get("dictionary.key1", ""), "value1"); EXPECT_EQ(test_data.get("dictionary.key3", ""), "value3"); EXPECT_EQ(test_data.count("list"), 1); EXPECT_EQ(test_data.get_child("list").count(""), 2); } }
Status YARAEventSubscriber::init() { Status status; ConfigDataInstance config; const auto& yara_config = config.getParsedData("yara"); if (yara_config.count("file_paths") == 0) return Status(0, "OK"); const auto& yara_paths = yara_config.get_child("file_paths"); const auto& file_map = config.files(); for (const auto& yara_path_element : yara_paths) { // Subscribe to each file for the given key (category). if (file_map.count(yara_path_element.first) == 0) { VLOG(1) << "Key in yara.file_paths not found in file_paths: " << yara_path_element.first; continue; } for (const auto& file : file_map.at(yara_path_element.first)) { VLOG(1) << "Added YARA listener to: " << file; auto mc = createSubscriptionContext(); mc->path = file; mc->mask = FILE_CHANGE_MASK; mc->recursive = true; subscribe(&YARAEventSubscriber::Callback, mc, (void*)(&yara_path_element.first)); } } return Status(0, "OK"); }
Status YARAEventSubscriber::Callback(const FileEventContextRef& ec, const void* user_data) { if (user_data == nullptr) { return Status(1, "No YARA category string provided"); } if (ec->action != "UPDATED" && ec->action != "CREATED") { return Status(1, "Invalid action"); } Row r; r["action"] = ec->action; r["target_path"] = ec->path; r["category"] = *(std::string*)user_data; // Only FSEvents transactions updates (inotify is a no-op). r["transaction_id"] = INTEGER(ec->transaction_id); // These are default values, to be updated in YARACallback. r["count"] = INTEGER(0); r["matches"] = std::string(""); r["strings"] = std::string(""); r["tags"] = std::string(""); ConfigDataInstance config; const auto& parser = config.getParser("yara"); if (parser == nullptr) return Status(1, "ConfigParser unknown."); const auto& yaraParser = std::static_pointer_cast<YARAConfigParserPlugin>(parser); auto rules = yaraParser->rules(); // Use the category as a lookup into the yara file_paths. The value will be // a list of signature groups to scan with. auto category = r.at("category"); const auto& yara_config = config.getParsedData("yara"); const auto& yara_paths = yara_config.get_child("file_paths"); const auto& sig_groups = yara_paths.find(category); for (const auto& rule : sig_groups->second) { const std::string group = rule.second.data(); int result = yr_rules_scan_file(rules[group], ec->path.c_str(), SCAN_FLAGS_FAST_MODE, YARACallback, (void*)&r, 0); if (result != ERROR_SUCCESS) { return Status(1, "YARA error: " + std::to_string(result)); } } if (ec->action != "" && r.at("matches").size() > 0) { add(r, ec->time); } return Status(0, "OK"); }