blob: 84af243b5c7dc5fe6a638732064d67280f68b21b [file]
/**
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <map>
#include <string>
#include "ConsumeWindowsEventLog.h"
#include "unit/TestBase.h"
#include "unit/Catch.h"
#include "core/Core.h"
#include "utils/OsUtils.h"
#include "wel/MetadataWalker.h"
#include "wel/XMLString.h"
#include "pugixml.hpp"
using Metadata = org::apache::nifi::minifi::wel::Metadata;
using MetadataWalker = org::apache::nifi::minifi::wel::MetadataWalker;
using WindowsEventLogProvider = org::apache::nifi::minifi::wel::WindowsEventLogProvider;
using WindowsEventLogMetadata = org::apache::nifi::minifi::wel::WindowsEventLogMetadata;
using WindowsEventLogMetadataImpl = org::apache::nifi::minifi::wel::WindowsEventLogMetadataImpl;
using XmlString = org::apache::nifi::minifi::wel::XmlString;
namespace {
auto none_matcher = minifi::wel::parseSidMatcher(std::nullopt);
std::string updateXmlMetadata(const std::string &xml, EVT_HANDLE provider_handle, EVT_HANDLE event_handle, bool update_xml, bool resolve,
const std::function<bool(std::string_view)>& sid_matcher = none_matcher) {
WindowsEventLogProvider event_log_provider{provider_handle};
WindowsEventLogMetadataImpl metadata{event_log_provider, event_handle};
MetadataWalker walker(metadata, "", update_xml, resolve, sid_matcher, &utils::OsUtils::userIdToUsername);
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(xml.c_str());
if (result) {
doc.traverse(walker);
XmlString writer;
doc.print(writer, "", pugi::format_raw); // no indentation or formatting
return writer.xml_;
} else {
throw std::runtime_error("Could not parse XML document");
}
}
std::string formatXml(const std::string &xml) {
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(xml.c_str());
if (result) {
XmlString writer;
doc.print(writer, "", pugi::format_raw); // no indentation or formatting
return writer.xml_;
}
return xml;
}
std::string readFile(const std::string &file_name) {
std::ifstream file{file_name};
return std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
}
const std::string METADATA_WALKER_TESTS_LOG_NAME = "MetadataWalkerTests";
const short event_type_index = 178; // NOLINT short comes from WINDOWS API
class FakeWindowsEventLogMetadata : public WindowsEventLogMetadata {
public:
[[nodiscard]] std::string getEventData(EVT_FORMAT_MESSAGE_FLAGS field, const std::string&) const override {
return "event_data_for_field_" + std::to_string(field);
}
[[nodiscard]] std::string getEventTimestamp() const override { return "event_timestamp"; }
short getEventTypeIndex() const override { return event_type_index; } // NOLINT short comes from WINDOWS API
};
} // namespace
TEST_CASE("MetadataWalker updates the Sid in the XML if both update_xml and resolve are true", "[updateXmlMetadata]") {
std::string xml = readFile("resources/nobodysid.xml");
SECTION("No resolution") {
REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, false, true) == formatXml(xml));
}
SECTION("Resolve nobody") {
std::string nobody = readFile("resources/withsids.xml");
const auto sid_matcher = minifi::wel::parseSidMatcher(".*Sid");
REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, true, true, sid_matcher) == formatXml(nobody));
}
}
TEST_CASE("MetadataWalker updates the Security/UserId attribute", "[updateXmlMetadata][userid]") {
std::string xml = readFile("resources/userid.xml");
SECTION("No resolution") {
REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, false, true) == formatXml(xml));
}
SECTION("Resolve nobody") {
std::string nobody = readFile("resources/resolveduserid.xml");
const auto sid_matcher = minifi::wel::parseSidMatcher("(.*Sid)|UserID");
REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, true, true, sid_matcher) == formatXml(nobody));
}
}
TEST_CASE("MetadataWalker works even when there is no Data block", "[updateXmlMetadata]") {
std::string xml = readFile("resources/nodata.xml");
REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, false, true) == formatXml(xml));
}
TEST_CASE("MetadataWalker throws if the input XML is invalid", "[updateXmlMetadata]") {
std::string xml = readFile("resources/invalidxml.xml");
REQUIRE_THROWS(updateXmlMetadata(xml, nullptr, nullptr, false, true) == formatXml(xml));
}
TEST_CASE("MetadataWalker will leave a Sid unchanged if it doesn't correspond to a user", "[updateXmlMetadata]") {
std::string xml = readFile("resources/unknownsid.xml");
REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, false, true) == formatXml(xml));
REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, true, true) == formatXml(xml));
const auto sid_matcher = minifi::wel::parseSidMatcher(".*Sid");
REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, true, true, sid_matcher) == formatXml(xml));
}
TEST_CASE("MetadataWalker can replace multiple Sids", "[updateXmlMetadata]") {
std::string xml = readFile("resources/multiplesids.xml");
pugi::xml_document doc;
xml = updateXmlMetadata(xml, nullptr, nullptr, false, true);
pugi::xml_parse_result result = doc.load_string(xml.c_str());
REQUIRE(result);
// we are only testing multiple sid resolutions, not the resolution of other items.
CHECK(std::string_view("Nobody Everyone Null Authority") == doc.select_node("Event/EventData/Data[@Name='GroupMembership']").node().text().get());
}
namespace {
void extractMappingsTestHelper(const std::string &file_name,
bool update_xml,
bool resolve,
const std::map<std::string, std::string>& expected_identifiers,
const std::map<Metadata, std::string>& expected_metadata,
const std::map<std::string, std::string>& expected_field_values) {
std::string input_xml = readFile(file_name);
REQUIRE(!input_xml.empty());
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(input_xml.c_str());
REQUIRE(result);
const auto sid_matcher = minifi::wel::parseSidMatcher(".*Sid");
MetadataWalker walker(FakeWindowsEventLogMetadata{}, METADATA_WALKER_TESTS_LOG_NAME, update_xml, resolve, sid_matcher, &utils::OsUtils::userIdToUsername);
doc.traverse(walker);
CHECK(walker.getIdentifiers() == expected_identifiers);
CHECK(walker.getFieldValues() == expected_field_values);
for (const auto &key_value_pair : expected_metadata) {
CHECK(walker.getMetadata(key_value_pair.first) == key_value_pair.second);
}
}
} // namespace
TEST_CASE("MetadataWalker extracts mappings correctly when there is a single Sid and resolve=false", "[for_each]") {
const std::string file_name = "resources/nobodysid.xml";
const std::map<std::string, std::string> expected_identifiers{{"S-1-0-0", "S-1-0-0"}};
using org::apache::nifi::minifi::wel::Metadata;
const std::map<Metadata, std::string> expected_metadata{
{Metadata::SOURCE, "Microsoft-Windows-Security-Auditing"},
{Metadata::TIME_CREATED, "event_timestamp"},
{Metadata::EVENTID, "4672"},
{Metadata::EVENT_RECORDID, "2575952"}};
const std::map<std::string, std::string> expected_field_values{};
SECTION("update_xml is false") {
extractMappingsTestHelper(file_name, false, false, expected_identifiers, expected_metadata, expected_field_values);
}
SECTION("update_xml is true") {
extractMappingsTestHelper(file_name, true, false, expected_identifiers, expected_metadata, expected_field_values);
}
}
TEST_CASE("MetadataWalker extracts mappings correctly when there is a single Sid and resolve=true", "[for_each]") {
const std::string file_name = "resources/nobodysid.xml";
const std::map<std::string, std::string> expected_identifiers{{"S-1-0-0", "Nobody"}};
using org::apache::nifi::minifi::wel::Metadata;
const std::map<Metadata, std::string> expected_metadata{
{Metadata::LOG_NAME, "MetadataWalkerTests"},
{Metadata::SOURCE, "Microsoft-Windows-Security-Auditing"},
{Metadata::TIME_CREATED, "event_timestamp"},
{Metadata::EVENTID, "4672"},
{Metadata::OPCODE, "event_data_for_field_4"},
{Metadata::EVENT_RECORDID, "2575952"},
{Metadata::EVENT_TYPE, "178"},
{Metadata::TASK_CATEGORY, "event_data_for_field_3"},
{Metadata::LEVEL, "event_data_for_field_2"},
{Metadata::KEYWORDS, "event_data_for_field_5"}};
SECTION("update_xml is false => fields are collected into walker.getFieldValues()") {
const std::map<std::string, std::string> expected_field_values{
{"Channel", "event_data_for_field_6"},
{"Keywords", "event_data_for_field_5"},
{"Level", "event_data_for_field_2"},
{"Opcode", "event_data_for_field_4"},
{"SubjectUserSid", "Nobody"},
{"Task", "event_data_for_field_3"}};
extractMappingsTestHelper(file_name, false, true, expected_identifiers, expected_metadata, expected_field_values);
}
SECTION("update_xml is true => fields are updated in-place in the XML, and walker.getFieldValues() is empty") {
const std::map<std::string, std::string> expected_field_values{};
extractMappingsTestHelper(file_name, true, true, expected_identifiers, expected_metadata, expected_field_values);
}
}
TEST_CASE("MetadataWalker extracts mappings correctly when there are multiple Sids and resolve=false", "[for_each]") {
const std::string file_name = "resources/multiplesids.xml";
const std::map<std::string, std::string> expected_identifiers{{"S-1-0-0", "S-1-0-0"}};
using org::apache::nifi::minifi::wel::Metadata;
const std::map<Metadata, std::string> expected_metadata{
{Metadata::SOURCE, "Microsoft-Windows-Security-Auditing"},
{Metadata::TIME_CREATED, "event_timestamp"},
{Metadata::EVENTID, "4672"},
{Metadata::EVENT_RECORDID, "2575952"}};
const std::map<std::string, std::string> expected_field_values{};
SECTION("update_xml is false") {
extractMappingsTestHelper(file_name, false, false, expected_identifiers, expected_metadata, expected_field_values);
}
SECTION("update_xml is true") {
extractMappingsTestHelper(file_name, true, false, expected_identifiers, expected_metadata, expected_field_values);
}
}
TEST_CASE("MetadataWalker extracts mappings correctly when there are multiple Sids and resolve=true", "[for_each]") {
const std::string file_name = "resources/multiplesids.xml";
const std::map<std::string, std::string> expected_identifiers{
{"%{S-1-0}", "Null Authority"},
{"%{S-1-0-0}", "Nobody"},
{"%{S-1-1-0}", "Everyone"},
{"S-1-0", "Null Authority"},
{"S-1-0-0", "Nobody"},
{"S-1-1-0", "Everyone"}};
using org::apache::nifi::minifi::wel::Metadata;
const std::map<Metadata, std::string> expected_metadata{
{Metadata::LOG_NAME, "MetadataWalkerTests"},
{Metadata::SOURCE, "Microsoft-Windows-Security-Auditing"},
{Metadata::TIME_CREATED, "event_timestamp"},
{Metadata::EVENTID, "4672"},
{Metadata::OPCODE, "event_data_for_field_4"},
{Metadata::EVENT_RECORDID, "2575952"},
{Metadata::EVENT_TYPE, "178"},
{Metadata::TASK_CATEGORY, "event_data_for_field_3"},
{Metadata::LEVEL, "event_data_for_field_2"},
{Metadata::KEYWORDS, "event_data_for_field_5"}};
SECTION("update_xml is false => fields are collected into walker.getFieldValues()") {
const std::map<std::string, std::string> expected_field_values{
{"Channel", "event_data_for_field_6"},
{"Keywords", "event_data_for_field_5"},
{"Level", "event_data_for_field_2"},
{"Opcode", "event_data_for_field_4"},
{"SubjectUserSid", "Nobody"},
{"Task", "event_data_for_field_3"}};
extractMappingsTestHelper(file_name, false, true, expected_identifiers, expected_metadata, expected_field_values);
}
SECTION("update_xml is true => fields are updated in-place in the XML, and walker.getFieldValues() is empty") {
const std::map<std::string, std::string> expected_field_values{};
extractMappingsTestHelper(file_name, true, true, expected_identifiers, expected_metadata, expected_field_values);
}
}
TEST_CASE("MetadataWalker extracts mappings correctly when the Sid is unknown and resolve=false", "[for_each]") {
const std::string file_name = "resources/unknownsid.xml";
const std::map<std::string, std::string> expected_identifiers{{"S-1-8-6-5-3-0-9", "S-1-8-6-5-3-0-9"}};
using org::apache::nifi::minifi::wel::Metadata;
const std::map<Metadata, std::string> expected_metadata{
{Metadata::SOURCE, "Microsoft-Windows-Security-Auditing"},
{Metadata::TIME_CREATED, "event_timestamp"},
{Metadata::EVENTID, "4672"},
{Metadata::EVENT_RECORDID, "2575952"}};
const std::map<std::string, std::string> expected_field_values{};
SECTION("update_xml is false") {
extractMappingsTestHelper(file_name, false, false, expected_identifiers, expected_metadata, expected_field_values);
}
SECTION("update_xml is true") {
extractMappingsTestHelper(file_name, true, false, expected_identifiers, expected_metadata, expected_field_values);
}
}
TEST_CASE("MetadataWalker extracts mappings correctly when the Sid is unknown and resolve=true", "[for_each]") {
const std::string file_name = "resources/unknownsid.xml";
const std::map<std::string, std::string> expected_identifiers{{"S-1-8-6-5-3-0-9", "S-1-8-6-5-3-0-9"}};
using org::apache::nifi::minifi::wel::Metadata;
const std::map<Metadata, std::string> expected_metadata{
{Metadata::LOG_NAME, "MetadataWalkerTests"},
{Metadata::SOURCE, "Microsoft-Windows-Security-Auditing"},
{Metadata::TIME_CREATED, "event_timestamp"},
{Metadata::EVENTID, "4672"},
{Metadata::OPCODE, "event_data_for_field_4"},
{Metadata::EVENT_RECORDID, "2575952"},
{Metadata::EVENT_TYPE, "178"},
{Metadata::TASK_CATEGORY, "event_data_for_field_3"},
{Metadata::LEVEL, "event_data_for_field_2"},
{Metadata::KEYWORDS, "event_data_for_field_5"}};
SECTION("update_xml is false => fields are collected into walker.getFieldValues()") {
const std::map<std::string, std::string> expected_field_values{
{"Channel", "event_data_for_field_6"},
{"Keywords", "event_data_for_field_5"},
{"Level", "event_data_for_field_2"},
{"Opcode", "event_data_for_field_4"},
{"Task", "event_data_for_field_3"}};
extractMappingsTestHelper(file_name, false, true, expected_identifiers, expected_metadata, expected_field_values);
}
SECTION("update_xml is true => fields are updated in-place in the XML, and walker.getFieldValues() is empty") {
const std::map<std::string, std::string> expected_field_values{};
extractMappingsTestHelper(file_name, true, true, expected_identifiers, expected_metadata, expected_field_values);
}
}