Skip to content

Commit

Permalink
feat: Initial changes for windoes event reader and fix file test for …
Browse files Browse the repository at this point in the history
…windows
  • Loading branch information
LucioDonda committed Dec 24, 2024
1 parent 2a0056b commit 5eb5ec3
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 17 deletions.
9 changes: 9 additions & 0 deletions etc/config/wazuh-agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,12 @@ logcollector:
- /var/log/auth.log
reload_interval: 1m
file_wait: 500ms
windows:
- channel: Application
query: Event[System/EventID = 4624]
use-bookmark: true
reconnect-time: 5s
- channel: System
query: Event[System/EventID = 7040]
use-bookmark: false
reconnect-time: 5s
4 changes: 4 additions & 0 deletions src/cmake/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ set(DEFAULT_FILE_WAIT 500 CACHE STRING "Default Logcollector file reading interv

set(DEFAULT_RELOAD_INTERVAL 60000 CACHE STRING "Default Logcollector reload interval (1m)")

set(DEFAULT_RECONNECT_TIME 5000 CACHE STRING "Default Logcollector reconnect time (5000ms)")

set(DEFAULT_USE_BOOKMARK false CACHE BOOL "Default Logcollector windows bookmark enabled (false)")

set(DEFAULT_INVENTORY_ENABLED true CACHE BOOL "Default inventory enabled")

set(DEFAULT_INTERVAL 3600000 CACHE STRING "Default inventory interval (1h)")
Expand Down
2 changes: 2 additions & 0 deletions src/common/config/include/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ namespace config
constexpr auto DEFAULT_FILE_WAIT = @DEFAULT_FILE_WAIT@;
constexpr auto DEFAULT_RELOAD_INTERVAL = @DEFAULT_RELOAD_INTERVAL@;
constexpr auto DEFAULT_LOCALFILES = "/var/log/auth.log";
constexpr auto DEFAULT_RECONNECT_TIME = @DEFAULT_RECONNECT_TIME@;
constexpr auto DEFAULT_USE_BOOKMARK = @DEFAULT_USE_BOOKMARK@;
}

namespace inventory
Expand Down
6 changes: 4 additions & 2 deletions src/modules/logcollector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ logcollector:
delim-regex: "\n"
use-bookmark: true
windows:
reconnect-time: 5s
use-bookmark: true
- channel: Application
query: Event[System/EventID = 4624]
use-bookmark: true
reconnect-time: 5s
- channel: System
query: Event[System/EventID = 7040]
journald:
- filter:
- field: "_SYSTEMD_UNIT"
Expand Down
6 changes: 6 additions & 0 deletions src/modules/logcollector/include/logcollector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ class Logcollector {
/// @param configurationParser Configuration parser
void SetupFileReader(const std::shared_ptr<const configuration::ConfigurationParser> configurationParser);

#ifdef _WIN32
/// @brief Sets up the Windows Event Channel reader
/// @param configurationParser Configuration parser
void SetupWEReader(const std::shared_ptr<const configuration::ConfigurationParser> configurationParser);
#endif

private:
/// @brief Module name
const std::string m_moduleName = "logcollector";
Expand Down
26 changes: 26 additions & 0 deletions src/modules/logcollector/src/logcollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@

#include <chrono>
#include <iomanip>
#include <map>
#include <sstream>

#include "file_reader.hpp"

#ifdef _WIN32
#include "we_reader_win.hpp"
#endif
using namespace logcollector;


Expand Down Expand Up @@ -58,6 +62,28 @@ void Logcollector::SetupFileReader(const std::shared_ptr<const configuration::Co
}
}

#ifdef _WIN32
void Logcollector::SetupWEReader(const std::shared_ptr<const configuration::ConfigurationParser> configurationParser) {
const auto reconnectTime = configurationParser->GetConfig<time_t>("logcollector", "reconnect-time").value_or(config::logcollector::DEFAULT_RECONNECT_TIME);

const auto bookmarkEnabled = configurationParser->GetConfig<bool>("logcollector", "use-bookmark").value_or(config::logcollector::DEFAULT_USE_BOOKMARK);

const auto windowsConfig = configurationParser->GetConfig<std::vector<std::map<std::string, std::string>>>("logcollector", "windows").value_or(
std::vector<std::map<std::string, std::string>> {});

std::list<std::string> channelsList;
std::list<std::string> queriesList;
for (auto& entry : windowsConfig)
{
auto channel = entry.at("channel");
channelsList.emplace_back(channel);
auto query = entry.at("query");
queriesList.emplace_back(query);
}
AddReader(std::make_shared<WindowsEventTracerReader>(*this, channelsList, queriesList, reconnectTime, bookmarkEnabled));
}
#endif

void Logcollector::Stop() {
m_ioContext.stop();
LogInfo("Logcollector module stopped.");
Expand Down
1 change: 1 addition & 0 deletions src/modules/logcollector/src/reader.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <boost/asio/awaitable.hpp>
#include <logcollector.hpp>

namespace logcollector {

Expand Down
39 changes: 39 additions & 0 deletions src/modules/logcollector/src/we_reader_win.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "we_reader_win.hpp"

#include <logger.hpp>

using namespace logcollector;

WindowsEventTracerReader::WindowsEventTracerReader(Logcollector &logcollector,
const std::list<std::string> channels,
const std::list<std::string> queries,
const std::time_t reloadInterval,
bool bookmarkEnabled) :
IReader(logcollector),
m_channelsList(channels),
m_queriesList(queries),
m_ReaderReloadInterval(reloadInterval),
m_bookmarkEnabled(bookmarkEnabled) { }

Awaitable WindowsEventTracerReader::Run()
{
// TODO: add lambda for loop break
while (true) {
m_logcollector.EnqueueTask(ReadEventChannel());

co_await m_logcollector.Wait(
std::chrono::milliseconds(m_ReaderReloadInterval));
}
}

Awaitable WindowsEventTracerReader::ReadEventChannel()
{
for (auto eventChannel : m_channelsList)
{
const std::string log {};
m_logcollector.SendMessage(eventChannel, log, m_collectorType);

co_await m_logcollector.Wait(std::chrono::milliseconds(m_ReaderReloadInterval));
}
}

52 changes: 52 additions & 0 deletions src/modules/logcollector/src/we_reader_win.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#ifdef _WIN32
#pragma once

#include <ctime>
#include <list>
#include <string>

#include "reader.hpp"

namespace logcollector {

/// @brief Windows Event Tracer Reader class
/// TODO: doc
class WindowsEventTracerReader : public IReader
{
public:
/// @brief Constructor for the Windows Event Tracer Reader
/// @param logcollector Log collector instance
/// @param channels List of channel names
/// @param queries List of queries
/// @param reloadInterval
/// @param bookmarkEnabled
WindowsEventTracerReader(Logcollector &logcollector,
const std::list<std::string> channels,
const std::list<std::string> queries,
const std::time_t reloadInterval,
bool bookmarkEnabled);

/// @brief Runs the file reader
/// @return Awaitable result
Awaitable Run() override;

// TODO: doc
Awaitable ReadEventChannel();

private:
/// @brief
std::time_t m_ReaderReloadInterval;

std::list<std::string> m_channelsList;

std::list<std::string> m_queriesList;

bool m_bookmarkEnabled;

int m_availableEvents = 0;

const std::string m_collectorType = "eventchannel";
};

} // namespace logcollector
#endif
39 changes: 28 additions & 11 deletions src/modules/logcollector/tests/unit/file_reader_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@

using namespace logcollector;

#ifdef _WIN32
static constexpr auto TMP_FILE_DIR = "C:\\Temp\\";
#else
static constexpr auto TMP_FILE_DIR = "/tmp/"
#endif

inline std::string getFullFileName(const std::string& filename) {
//TODO: move to setup stage of test only for windows
std::filesystem::create_directories(TMP_FILE_DIR);
return TMP_FILE_DIR + filename;
}

class MockCallback {
public:
MOCK_METHOD(void, Call, (const std::string &), ());
Expand Down Expand Up @@ -52,8 +64,8 @@ TEST(Localfile, OpenError)

TEST(Localfile, Rotated)
{
auto fileA = TempFile("/tmp/A.log", "Hello World");
auto lf = Localfile("/tmp/A.log");
auto fileA = TempFile(getFullFileName("A.log"), "Hello World");
auto lf = Localfile(getFullFileName("A.log"));

lf.SeekEnd();
ASSERT_FALSE(lf.Rotated());
Expand All @@ -64,6 +76,10 @@ TEST(Localfile, Rotated)

TEST(Localfile, Deleted)
{
#ifdef _WIN32
//FIXME: The process cannot access the file because it is being used by another process.
GTEST_SKIP();
#endif
auto fileA = std::make_unique<TempFile>("/tmp/A.log", "Hello World");
auto lf = Localfile("/tmp/A.log");

Expand All @@ -81,18 +97,19 @@ TEST(FileReader, Reload) {
spdlog::default_logger()->sinks().clear();
MockCallback mockCallback;

EXPECT_CALL(mockCallback, Call("/tmp/A.log")).Times(1);
EXPECT_CALL(mockCallback, Call("/tmp/B.log")).Times(1);
EXPECT_CALL(mockCallback, Call("/tmp/C.log")).Times(1);
EXPECT_CALL(mockCallback, Call("/tmp/D.log")).Times(1);
EXPECT_CALL(mockCallback, Call(getFullFileName("A.log"))).Times(1);
EXPECT_CALL(mockCallback, Call(getFullFileName("B.log"))).Times(1);
EXPECT_CALL(mockCallback, Call(getFullFileName("C.log"))).Times(1);
EXPECT_CALL(mockCallback, Call(getFullFileName("D.log"))).Times(1);

auto a = TempFile("/tmp/A.log");
auto b = TempFile("/tmp/B.log");
auto c = TempFile("/tmp/C.log");
auto a = TempFile(getFullFileName("A.log"));
auto b = TempFile(getFullFileName("B.log"));
auto c = TempFile(getFullFileName("C.log"));

FileReader reader(Logcollector::Instance(), "/tmp/*.log", 500, 60000); //NOLINT
auto regex = TMP_FILE_DIR + std::string("*.log");
FileReader reader(Logcollector::Instance(), regex, 500, 60000); //NOLINT
reader.Reload([&](Localfile& lf) { mockCallback.Call(lf.Filename()); });

auto d = TempFile("/tmp/D.log");
auto d = TempFile(getFullFileName("D.log"));
reader.Reload([&](Localfile& lf) { mockCallback.Call(lf.Filename()); });
}
4 changes: 4 additions & 0 deletions src/modules/logcollector/tests/unit/logcollector_mock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class LogcollectorMock : public Logcollector {
Logcollector::SetupFileReader(configurationParser);
}

void SetupWEReader(const std::shared_ptr<const configuration::ConfigurationParser> configurationParser) {
Logcollector::SetupWEReader(configurationParser);
}

MOCK_METHOD(void, AddReader, (std::shared_ptr<IReader> reader), (override));
MOCK_METHOD(void, EnqueueTask, (Awaitable task), (override));
};
Expand Down
28 changes: 28 additions & 0 deletions src/modules/logcollector/tests/unit/logcollector_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,31 @@ TEST(Logcollector, SendMessage) {
ASSERT_EQ(capturedMessage.data["event"]["provider"], PROVIDER);
ASSERT_EQ(capturedMessage.metaData, METADATA);
}

TEST(Logcollector, SetupWECReader) {
auto constexpr CONFIG_RAW = R"(
logcollector:
windows:
- channel: Application
query: Event[System/EventID = 4624]
- channel: System
query: Event[System/EventID = 7040]
)";

std::shared_ptr<IReader> capturedReader1;
auto logcollector = LogcollectorMock();
auto config = std::make_shared<configuration::ConfigurationParser>(std::string(CONFIG_RAW));

EXPECT_CALL(logcollector, AddReader(::testing::_)).Times(1)
.WillOnce(::testing::SaveArg<0>(&capturedReader1));

logcollector.SetupWEReader(config);

ASSERT_NE(capturedReader1, nullptr);
}

int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
37 changes: 33 additions & 4 deletions src/modules/logcollector/tests/unit/tempfile.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
#pragma once

#include <filesystem>
#include <fstream>
#include <iostream>

class TempFile {
public:
TempFile(std::string path, const std::string& str = "") :
m_path(std::move(path)),
m_stream(m_path)
m_path(std::move(path))
{
m_stream.open(m_path, std::ios::out | std::ios::binary);

if (!str.empty()) {
Write(str);
}
Expand All @@ -19,13 +22,39 @@ class TempFile {
}

void Truncate() {
// Close and reopen the file to ensure Windows allows the resize
m_stream.close();
std::filesystem::resize_file(m_path, 0);
m_stream.seekp(0);
m_stream.open(m_path, std::ios::out | std::ios::binary | std::ios::trunc);
}

~TempFile() {
m_stream.close();

std::error_code ec;
std::filesystem::remove(m_path, ec);
if (!std::filesystem::remove(m_path, ec))
{
auto error_msg = std::system_category().message(ec.value());

// Alternative approach using Windows API directly
#ifdef _WIN32
std::wstring wpath(m_path.begin(), m_path.end());
if (DeleteFileW(wpath.c_str()) == 0) {
DWORD win_error = GetLastError();
char win_error_msg[256];
FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
win_error,
0,
win_error_msg,
sizeof(win_error_msg),
NULL
);
// win_error_msg (from Windows API)
}
#endif
}
}

const std::string& Path() const {
Expand Down
Loading

0 comments on commit 5eb5ec3

Please sign in to comment.