diff --git a/src/agent/CMakeLists.txt b/src/agent/CMakeLists.txt index 657e960495..2a6d2fa426 100644 --- a/src/agent/CMakeLists.txt +++ b/src/agent/CMakeLists.txt @@ -49,7 +49,22 @@ list(APPEND SOURCES add_library(Agent ${SOURCES}) target_include_directories(Agent PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) -target_link_libraries(Agent PUBLIC ConfigurationParser Communicator AgentInfo CommandHandler MultiTypeQueue ModuleManager ModuleCommand PRIVATE OpenSSL::SSL OpenSSL::Crypto Boost::asio Boost::beast) +target_link_libraries(Agent + PUBLIC + ConfigurationParser + Communicator + AgentInfo + CommandHandler + MultiTypeQueue + ModuleManager + ModuleCommand + CentralizedConfiguration + PRIVATE + OpenSSL::SSL + OpenSSL::Crypto + Boost::asio + Boost::beast +) include(../cmake/ConfigureTarget.cmake) configure_target(Agent) diff --git a/src/agent/configuration_parser/CMakeLists.txt b/src/agent/configuration_parser/CMakeLists.txt index 64925cdee2..4931bfa182 100644 --- a/src/agent/configuration_parser/CMakeLists.txt +++ b/src/agent/configuration_parser/CMakeLists.txt @@ -14,7 +14,7 @@ find_package(toml11 CONFIG REQUIRED) add_library(ConfigurationParser src/configuration_parser.cpp) target_include_directories(ConfigurationParser PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) -target_link_libraries(ConfigurationParser PRIVATE toml11::toml11 Logger) +target_link_libraries(ConfigurationParser PUBLIC toml11::toml11 Logger) include(../../cmake/ConfigureTarget.cmake) configure_target(ConfigurationParser) diff --git a/src/agent/include/agent.hpp b/src/agent/include/agent.hpp index fab41be87a..eb08e6791b 100644 --- a/src/agent/include/agent.hpp +++ b/src/agent/include/agent.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -32,4 +33,5 @@ class Agent communicator::Communicator m_communicator; command_handler::CommandHandler m_commandHandler; ModuleManager m_moduleManager; + centralized_configuration::CentralizedConfiguration m_centralizedConfiguration; }; diff --git a/src/agent/src/agent.cpp b/src/agent/src/agent.cpp index c1ef43d5c9..77bd000d4e 100644 --- a/src/agent/src/agent.cpp +++ b/src/agent/src/agent.cpp @@ -23,6 +23,15 @@ Agent::Agent(const std::string& configPath, std::unique_ptr sign { return m_configurationParser.GetConfig(std::move(table), std::move(key)); }) , m_moduleManager(m_messageQueue, m_configurationParser) { + m_centralizedConfiguration.SetGroupIdFunction([this](const std::vector& groups) + { return m_agentInfo.SetGroups(groups); }); + + m_centralizedConfiguration.GetGroupIdFunction([this]() { return m_agentInfo.GetGroups(); }); + + m_centralizedConfiguration.SetDownloadGroupFilesFunction( + [this](const std::string& groupId, const std::string& destinationPath) + { return m_communicator.GetGroupConfigurationFromManager(groupId, destinationPath); }); + m_taskManager.Start(std::thread::hardware_concurrency()); } @@ -55,6 +64,7 @@ void Agent::Run() { return DispatchCommand(cmd, m_moduleManager.GetModule(cmd.Module), m_messageQueue); })); m_moduleManager.AddModule(Inventory::Instance()); + m_moduleManager.AddModule(m_centralizedConfiguration); m_moduleManager.Setup(); m_taskManager.EnqueueTask([this]() { m_moduleManager.Start(); }); diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 43a3e3815c..657dc241ae 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -5,6 +5,7 @@ project(ModuleManager) include(../cmake/CommonSettings.cmake) set_common_settings() +add_subdirectory(centralized_configuration) add_subdirectory(inventory) add_library(ModuleManager src/moduleManager.cpp) diff --git a/src/modules/centralized_configuration/CMakeLists.txt b/src/modules/centralized_configuration/CMakeLists.txt new file mode 100644 index 0000000000..67446ac014 --- /dev/null +++ b/src/modules/centralized_configuration/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.22) + +project(CentralizedConfiguration) + +include(../../cmake/CommonSettings.cmake) +set_common_settings() + +include_directories(${CMAKE_SOURCE_DIR}/common/logger/include) + +add_library(CentralizedConfiguration src/centralized_configuration.cpp) +target_include_directories(CentralizedConfiguration PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(CentralizedConfiguration PUBLIC ConfigurationParser ModuleManager MultiTypeQueue) + +include(../../cmake/ConfigureTarget.cmake) +configure_target(CentralizedConfiguration) + +if(BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/src/modules/centralized_configuration/include/centralized_configuration.hpp b/src/modules/centralized_configuration/include/centralized_configuration.hpp new file mode 100644 index 0000000000..78631159fb --- /dev/null +++ b/src/modules/centralized_configuration/include/centralized_configuration.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace centralized_configuration +{ + class CentralizedConfiguration + { + public: + using SetGroupIdFunctionType = std::function& groupList)>; + using GetGroupIdFunctionType = std::function()>; + using DownloadGroupFilesFunctionType = std::function; + + /** + * @brief Starts the centralized configuration process. + * @details This is a no-op function, required by the ModuleWrapper interface + * to be used within the ModuleManager. + */ + void Start() const; + + /** + * @brief Configures the centralized configuration system. + * @param configParser The configuration parser containing settings to be applied. + * @details This is a no-op function, required by the ModuleWrapper interface + * to be used within the ModuleManager. + */ + void Setup(const configuration::ConfigurationParser&) const; + + /** + * @brief Stops the centralized configuration process. + * @details This is a no-op function, required by the ModuleWrapper interface + * to be used within the ModuleManager. + */ + void Stop() const; + + /** + * @brief Executes a command for the centralized configuration system. + * @param command A string containing a JSON command to execute. + * @details Required by the ModuleWrapper interface to be used within the ModuleManager + * @return Co_CommandExecutionResult The result of executing the command, either success or failure. + */ + Co_CommandExecutionResult ExecuteCommand(std::string command); + + /** + * @brief Gets the name of the centralized configuration system. + * @details Required by the ModuleWrapper interface to be used within the ModuleManager + * @return A string representing the name. + */ + std::string Name() const; + + /** + * @brief Sets the message queue for the system. + * @param queue A shared pointer to the message queue used for communication. + * @details This is a no-op function, required by the ModuleWrapper interface + * to be used within the ModuleManager. + */ + void SetMessageQueue(const std::shared_ptr queue); + + /** + * @brief Sets the function to assign group IDs. + * @details The "set-group" commands requires such function to be set. + * @param setGroupIdFunction A function to set group IDs. + */ + void SetGroupIdFunction(SetGroupIdFunctionType setGroupIdFunction); + + /** + * @brief Sets the function to retrieve group IDs. + * @details The "update-group" commands requires such function to be set. + * @param getGroupIdFunction A function to get group IDs. + */ + void GetGroupIdFunction(GetGroupIdFunctionType getGroupIdFunction); + + /** + * @brief Sets the function to download group configuration files. + * @details Configures how and where to download group configuration files. + * These will be used to set and update the Agent groups via the "set-group" and "update-group" + * commands. + * @param downloadGroupFilesFunction A function to download files for a given group ID. + */ + void SetDownloadGroupFilesFunction(DownloadGroupFilesFunctionType downloadGroupFilesFunction); + + private: + SetGroupIdFunctionType m_setGroupIdFunction; + GetGroupIdFunctionType m_getGroupIdFunction; + DownloadGroupFilesFunctionType m_downloadGroupFilesFunction; + }; +} // namespace centralized_configuration diff --git a/src/modules/centralized_configuration/src/centralized_configuration.cpp b/src/modules/centralized_configuration/src/centralized_configuration.cpp new file mode 100644 index 0000000000..cc97fdfce6 --- /dev/null +++ b/src/modules/centralized_configuration/src/centralized_configuration.cpp @@ -0,0 +1,128 @@ +#include + +#include + +#include + +#include + +namespace centralized_configuration +{ + void CentralizedConfiguration::Start() const + { + } + + void CentralizedConfiguration::Setup(const configuration::ConfigurationParser&) const + { + } + + void CentralizedConfiguration::Stop() const + { + } + + // NOLINTNEXTLINE(performance-unnecessary-value-param) + Co_CommandExecutionResult CentralizedConfiguration::ExecuteCommand(const std::string command) + { + try + { + const auto commnandAsJson = nlohmann::json::parse(command); + + if (commnandAsJson["command"] == "set-group") + { + if (m_setGroupIdFunction && m_downloadGroupFilesFunction) + { + const auto groupIds = commnandAsJson["groups"].get>(); + + m_setGroupIdFunction(groupIds); + + for (const auto& groupId : groupIds) + { + m_downloadGroupFilesFunction( + groupId, + std::filesystem::temp_directory_path().string() + ); + } + + // TODO validate groupFiles, apply configuration + + co_return module_command::CommandExecutionResult{ + module_command::Status::SUCCESS, + "CentralizedConfiguration group set" + }; + } + else + { + co_return module_command::CommandExecutionResult{ + module_command::Status::FAILURE, + "CentralizedConfiguration group set failed, no function set" + }; + } + } + else if (commnandAsJson["command"] == "update-group") + { + if (m_getGroupIdFunction && m_downloadGroupFilesFunction) + { + const auto groupIds = m_getGroupIdFunction(); + + for (const auto& groupId : groupIds) + { + m_downloadGroupFilesFunction( + groupId, + std::filesystem::temp_directory_path().string() + ); + } + + // TODO validate groupFiles, apply configuration + + co_return module_command::CommandExecutionResult{ + module_command::Status::SUCCESS, + "CentralizedConfiguration group updated" + }; + } + else + { + co_return module_command::CommandExecutionResult{ + module_command::Status::FAILURE, + "CentralizedConfiguration group set failed, no function set" + }; + } + } + } + catch (const nlohmann::json::exception&) + { + co_return module_command::CommandExecutionResult{ + module_command::Status::FAILURE, + "CentralizedConfiguration error while parsing command" + }; + } + + co_return module_command::CommandExecutionResult{ + module_command::Status::FAILURE, + "CentralizedConfiguration command not recognized" + }; + } + + std::string CentralizedConfiguration::Name() const + { + return "CentralizedConfiguration"; + } + + void CentralizedConfiguration::SetMessageQueue(const std::shared_ptr) + { + } + + void CentralizedConfiguration::SetGroupIdFunction(SetGroupIdFunctionType setGroupIdFunction) + { + m_setGroupIdFunction = std::move(setGroupIdFunction); + } + + void CentralizedConfiguration::GetGroupIdFunction(GetGroupIdFunctionType getGroupIdFunction) + { + m_getGroupIdFunction = std::move(getGroupIdFunction); + } + + void CentralizedConfiguration::SetDownloadGroupFilesFunction(DownloadGroupFilesFunctionType downloadGroupFilesFunction) + { + m_downloadGroupFilesFunction = std::move(downloadGroupFilesFunction); + } +} // namespace centralized_configuration diff --git a/src/modules/centralized_configuration/tests/CMakeLists.txt b/src/modules/centralized_configuration/tests/CMakeLists.txt new file mode 100644 index 0000000000..6e6f6a990f --- /dev/null +++ b/src/modules/centralized_configuration/tests/CMakeLists.txt @@ -0,0 +1,7 @@ +find_package(GTest CONFIG REQUIRED) + +add_executable(centralized_configuration_test centralized_configuration_tests.cpp) +configure_target(centralized_configuration_test) +target_include_directories(centralized_configuration_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include) +target_link_libraries(centralized_configuration_test PRIVATE CentralizedConfiguration GTest::gtest GTest::gmock) +add_test(NAME CentralizedConfiguration COMMAND centralized_configuration_test) diff --git a/src/modules/centralized_configuration/tests/centralized_configuration_tests.cpp b/src/modules/centralized_configuration/tests/centralized_configuration_tests.cpp new file mode 100644 index 0000000000..56dfdaad8d --- /dev/null +++ b/src/modules/centralized_configuration/tests/centralized_configuration_tests.cpp @@ -0,0 +1,228 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +using centralized_configuration::CentralizedConfiguration; + +namespace +{ + // NOLINTBEGIN(cppcoreguidelines-avoid-reference-coroutine-parameters) + boost::asio::awaitable TestExecuteCommand( + CentralizedConfiguration& centralizedConfiguration, + const std::string& command, + module_command::Status expectedErrorCode) + { + const auto commandResult = co_await centralizedConfiguration.ExecuteCommand(command); + EXPECT_EQ(commandResult.ErrorCode, expectedErrorCode); + } + // NOLINTEND(cppcoreguidelines-avoid-reference-coroutine-parameters) +} + +TEST(CentralizedConfiguration, Constructor) +{ + EXPECT_NO_THROW( + [[maybe_unused]] CentralizedConfiguration centralizedConfiguration + ); +} + +TEST(CentralizedConfiguration, ImplementsModuleWrapperInterface) +{ + CentralizedConfiguration centralizedConfiguration; + EXPECT_NO_THROW(centralizedConfiguration.Start()); + EXPECT_NO_THROW(centralizedConfiguration.Stop()); + EXPECT_NO_THROW(centralizedConfiguration.Name()); + + const std::string emptyConfig; + configuration::ConfigurationParser configurationParser(emptyConfig); + EXPECT_NO_THROW(centralizedConfiguration.Setup(configurationParser)); +} + +TEST(CentralizedConfiguration, ExecuteCommandReturnsFailureOnUnrecognizedCommand) +{ + boost::asio::io_context io_context; + + boost::asio::co_spawn( + io_context, + [] () -> boost::asio::awaitable + { + CentralizedConfiguration centralizedConfiguration; + co_await TestExecuteCommand( + centralizedConfiguration, + R"({"command": "unknown-command"})", + module_command::Status::FAILURE + ); + }(), + boost::asio::detached + ); + + io_context.run(); +} + +TEST(CentralizedConfiguration, ExecuteCommandHandlesRecognizedCommands) +{ + boost::asio::io_context io_context; + + boost::asio::co_spawn( + io_context, + [] () -> boost::asio::awaitable + { + CentralizedConfiguration centralizedConfiguration; + centralizedConfiguration.SetGroupIdFunction( + [](const std::vector&) + { + } + ); + centralizedConfiguration.GetGroupIdFunction( + []() + { + return std::vector{"group1", "group2"}; + } + ); + centralizedConfiguration.SetDownloadGroupFilesFunction( + [](const std::string&, const std::string&) + { + return true; + } + ); + + co_await TestExecuteCommand( + centralizedConfiguration, + R"({"command": "set-group", "groups": ["group1", "group2"]})", + module_command::Status::SUCCESS + ); + + co_await TestExecuteCommand( + centralizedConfiguration, + R"({"command": "update-group"})", + module_command::Status::SUCCESS + ); + + co_await TestExecuteCommand( + centralizedConfiguration, + R"({"command": "unknown-command"})", + module_command::Status::FAILURE + ); + }(), + boost::asio::detached + ); + + io_context.run(); +} + +TEST(CentralizedConfiguration, SetFunctionsAreCalledAndReturnsCorrectResultsForSetGroup) +{ + boost::asio::io_context io_context; + + boost::asio::co_spawn( + io_context, + [] () -> boost::asio::awaitable + { + CentralizedConfiguration centralizedConfiguration; + + co_await TestExecuteCommand( + centralizedConfiguration, + R"({"command": "set-group", "groups": ["group1", "group2"]})", + module_command::Status::FAILURE + ); + + bool wasSetGroupIdFunctionCalled = false; + bool wasDownloadGroupFilesFunctionCalled = false; + + centralizedConfiguration.SetGroupIdFunction( + [&wasSetGroupIdFunctionCalled](const std::vector&) + { + wasSetGroupIdFunctionCalled = true; + } + ); + + centralizedConfiguration.SetDownloadGroupFilesFunction( + [&wasDownloadGroupFilesFunctionCalled](const std::string&, const std::string&) + { + wasDownloadGroupFilesFunctionCalled = true; + return wasDownloadGroupFilesFunctionCalled; + } + ); + + EXPECT_FALSE(wasSetGroupIdFunctionCalled); + EXPECT_FALSE(wasDownloadGroupFilesFunctionCalled); + + co_await TestExecuteCommand( + centralizedConfiguration, + R"({"command": "set-group", "groups": ["group1", "group2"]})", + module_command::Status::SUCCESS + ); + + EXPECT_TRUE(wasSetGroupIdFunctionCalled); + EXPECT_TRUE(wasDownloadGroupFilesFunctionCalled); + }(), + boost::asio::detached + ); + + io_context.run(); +} + +TEST(CentralizedConfiguration, SetFunctionsAreCalledAndReturnsCorrectResultsForUpdateGroup) +{ + boost::asio::io_context io_context; + + boost::asio::co_spawn( + io_context, + [] () -> boost::asio::awaitable + { + CentralizedConfiguration centralizedConfiguration; + + co_await TestExecuteCommand( + centralizedConfiguration, + R"({"command": "update-group"})", + module_command::Status::FAILURE + ); + + bool wasGetGroupIdFunctionCalled = false; + bool wasDownloadGroupFilesFunctionCalled = false; + + centralizedConfiguration.GetGroupIdFunction( + [&wasGetGroupIdFunctionCalled]() + { + wasGetGroupIdFunctionCalled = true; + return std::vector{"group1", "group2"}; + } + ); + + centralizedConfiguration.SetDownloadGroupFilesFunction( + [&wasDownloadGroupFilesFunctionCalled](const std::string&, const std::string&) + { + wasDownloadGroupFilesFunctionCalled = true; + return wasDownloadGroupFilesFunctionCalled; + } + ); + + EXPECT_FALSE(wasGetGroupIdFunctionCalled); + EXPECT_FALSE(wasDownloadGroupFilesFunctionCalled); + + co_await TestExecuteCommand( + centralizedConfiguration, + R"({"command": "update-group"})", + module_command::Status::SUCCESS + ); + + EXPECT_TRUE(wasGetGroupIdFunctionCalled); + EXPECT_TRUE(wasDownloadGroupFilesFunctionCalled); + }(), + boost::asio::detached + ); + + io_context.run(); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}