From e3a107d5f7199da077398b3b0703e577064fd1f7 Mon Sep 17 00:00:00 2001 From: Luis Enrique Chico Capistrano Date: Thu, 9 Jan 2025 17:18:47 -0300 Subject: [PATCH] feat: Implement self-restart functionality for the agent feat: Address comments and improve the code --- docs/ref/commands.md | 30 ++++++ packages/debs/SPECS/wazuh-agent/debian/postrm | 6 +- packages/debs/SPECS/wazuh-agent/debian/prerm | 27 +---- src/agent/CMakeLists.txt | 2 + .../include/command_entry/command_entry.hpp | 2 + .../command_handler/src/command_handler.cpp | 17 ++- src/agent/restart_handler/CMakeLists.txt | 27 +++++ .../include/restart_handler.hpp | 35 ++++++ .../src/restart_handler_unix.cpp | 100 ++++++++++++++++++ .../src/restart_handler_unix.hpp | 37 +++++++ .../src/restart_handler_win.cpp | 17 +++ src/agent/service/wazuh-agent.service | 5 +- src/agent/src/agent.cpp | 6 ++ src/agent/src/main.cpp | 2 + src/agent/src/unix/instance_handler_unix.cpp | 2 +- 15 files changed, 282 insertions(+), 33 deletions(-) create mode 100644 src/agent/restart_handler/CMakeLists.txt create mode 100644 src/agent/restart_handler/include/restart_handler.hpp create mode 100644 src/agent/restart_handler/src/restart_handler_unix.cpp create mode 100644 src/agent/restart_handler/src/restart_handler_unix.hpp create mode 100644 src/agent/restart_handler/src/restart_handler_win.cpp diff --git a/docs/ref/commands.md b/docs/ref/commands.md index f3fc49ceaa..15ecd136e7 100644 --- a/docs/ref/commands.md +++ b/docs/ref/commands.md @@ -63,3 +63,33 @@ Executing this command triggers a reload of the configuration and modules to ens } } ``` + + +## Restart Handler + +The restart handler is responsible for restarting the agent. This command does not require any arguments. + +Executing this command triggers a restart, which can be processed in two ways: + +- If the service was started by systemd, it will use the same command to restart the agent. +- If it was called manually, it will use the same command with the same arguments that were used to call it. + +```json +{ + "action": + { + "args": + {}, + "name": "restart", + "version": "5.0.0" + }, + "source": "Users/Services", + "document_id": "A8-62pMBBmC6Jrvqj9kW", + "user": "Management API", + "target": + { + "id": "d5b250c4-dfa1-4d94-827f-9f99210dbe6c", + "type": "agent" + } +} +``` diff --git a/packages/debs/SPECS/wazuh-agent/debian/postrm b/packages/debs/SPECS/wazuh-agent/debian/postrm index d4033a7ac2..015e8bf2e0 100644 --- a/packages/debs/SPECS/wazuh-agent/debian/postrm +++ b/packages/debs/SPECS/wazuh-agent/debian/postrm @@ -26,13 +26,17 @@ case "$1" in purge) + rm -rf /var/lib/wazuh-agent + # Remove the shared configuration folder if it exists + if [ -d "/etc/wazuh-agent/shared" ]; then + rm -rf "/etc/wazuh-agent/shared" + fi if getent passwd wazuh >/dev/null 2>&1; then deluser wazuh > /dev/null 2>&1 fi if getent group wazuh >/dev/null 2>&1; then delgroup wazuh > /dev/null 2>&1 fi - rm -rf /var/lib/wazuh-agent ;; upgrade) diff --git a/packages/debs/SPECS/wazuh-agent/debian/prerm b/packages/debs/SPECS/wazuh-agent/debian/prerm index 03ba9a4131..7c5853872e 100644 --- a/packages/debs/SPECS/wazuh-agent/debian/prerm +++ b/packages/debs/SPECS/wazuh-agent/debian/prerm @@ -15,18 +15,9 @@ case "$1" in elif ${BINARY_DIR}wazuh-agent --status 2>/dev/null | grep "is running" > /dev/null 2>&1; then pid=$(ps -ef | grep "${BINARY_DIR}wazuh-agent" | grep -v grep | awk '{print $2}') if [ -n "$pid" ]; then - kill -SIGTERM "$pid" 2>/dev/null + kill -15 "$pid" 2>/dev/null fi fi - - # # Process: wazuh-agent - # if pgrep -f "wazuh-agent" > /dev/null 2>&1; then - # kill -15 $(pgrep -f "wazuh-agent") > /dev/null 2>&1 - # fi - - # if pgrep -f "wazuh-agent" > /dev/null 2>&1; then - # kill -9 $(pgrep -f "wazuh-agent") > /dev/null 2>&1 - # fi ;; remove) @@ -38,10 +29,9 @@ case "$1" in elif ${BINARY_DIR}wazuh-agent --status 2>/dev/null | grep "is running" > /dev/null 2>&1; then pid=$(ps -ef | grep "${BINARY_DIR}wazuh-agent" | grep -v grep | awk '{print $2}') if [ -n "$pid" ]; then - kill -SIGTERM "$pid" 2>/dev/null + kill -15 "$pid" 2>/dev/null fi fi - ;; failed-upgrade) @@ -52,20 +42,9 @@ case "$1" in elif ${BINARY_DIR}wazuh-agent --status 2>/dev/null | grep "is running" > /dev/null 2>&1; then pid=$(ps -ef | grep "${BINARY_DIR}wazuh-agent" | grep -v grep | awk '{print $2}') if [ -n "$pid" ]; then - kill -SIGTERM "$pid" 2>/dev/null + kill -15 "$pid" 2>/dev/null fi fi - - # if [ -f ${INSTALLATION_WAZUH_DIR}/bin/wazuh-agent ]; then - # # pkill wazuh-agent - # if pgrep -f "wazuh-agent" > /dev/null 2>&1; then - # kill -15 $(pgrep -f "wazuh-agent") > /dev/null 2>&1 - # fi - - # if pgrep -f "wazuh-agent" > /dev/null 2>&1; then - # kill -9 $(pgrep -f "wazuh-agent") > /dev/null 2>&1 - # fi - # fi ;; *) diff --git a/src/agent/CMakeLists.txt b/src/agent/CMakeLists.txt index ea50e32e77..1575c5e131 100644 --- a/src/agent/CMakeLists.txt +++ b/src/agent/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(configuration_parser) add_subdirectory(multitype_queue) add_subdirectory(persistence) add_subdirectory(task_manager) +add_subdirectory(restart_handler) find_package(OpenSSL REQUIRED) find_package(Boost REQUIRED COMPONENTS asio beast system program_options) @@ -65,6 +66,7 @@ target_link_libraries(Agent MultiTypeQueue ModuleManager Boost::asio + RestartHandler sysinfo PRIVATE OpenSSL::SSL diff --git a/src/agent/command_handler/include/command_entry/command_entry.hpp b/src/agent/command_handler/include/command_entry/command_entry.hpp index 39fbe6dbb8..faba92009f 100644 --- a/src/agent/command_handler/include/command_entry/command_entry.hpp +++ b/src/agent/command_handler/include/command_entry/command_entry.hpp @@ -10,12 +10,14 @@ namespace module_command /// @brief Commands const std::string SET_GROUP_COMMAND = "set-group"; const std::string FETCH_CONFIG_COMMAND = "fetch-config"; + const std::string RESTART_COMMAND = "restart"; /// @brief Commands arguments const std::string GROUPS_ARG = "groups"; /// @brief Modules const std::string CENTRALIZED_CONFIGURATION_MODULE = "CentralizedConfiguration"; + const std::string RESTART_MODULE = "Restart"; /// @enum Status of a command execution enum class Status diff --git a/src/agent/command_handler/src/command_handler.cpp b/src/agent/command_handler/src/command_handler.cpp index bc4ac9bae8..185c41b2d0 100644 --- a/src/agent/command_handler/src/command_handler.cpp +++ b/src/agent/command_handler/src/command_handler.cpp @@ -17,7 +17,9 @@ namespace {{module_command::GROUPS_ARG, nlohmann::json::value_t::array}}}}, {module_command::FETCH_CONFIG_COMMAND, CommandDetails { - module_command::CENTRALIZED_CONFIGURATION_MODULE, module_command::CommandExecutionMode::SYNC, {}}}}; + module_command::CENTRALIZED_CONFIGURATION_MODULE, module_command::CommandExecutionMode::SYNC, {}}}, + {module_command::RESTART_COMMAND, + CommandDetails {module_command::RESTART_MODULE, module_command::CommandExecutionMode::SYNC, {}}}}; } // namespace namespace command_handler @@ -133,8 +135,17 @@ namespace command_handler { for (auto& cmd : cmds.value()) { - cmd.ExecutionResult.ErrorCode = module_command::Status::FAILURE; - cmd.ExecutionResult.Message = "Agent stopped during execution"; + if (cmd.Command == module_command::RESTART_COMMAND) + { + LogInfo("Agent restarted successfully"); + cmd.ExecutionResult.ErrorCode = module_command::Status::SUCCESS; + cmd.ExecutionResult.Message = "Agent restarted successfully"; + } + else + { + cmd.ExecutionResult.ErrorCode = module_command::Status::FAILURE; + cmd.ExecutionResult.Message = "Agent stopped during execution"; + } reportCommandResult(cmd); m_commandStore->UpdateCommand(cmd); } diff --git a/src/agent/restart_handler/CMakeLists.txt b/src/agent/restart_handler/CMakeLists.txt new file mode 100644 index 0000000000..b6001c8d74 --- /dev/null +++ b/src/agent/restart_handler/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.22) + +project(RestartHandler) + +include(../../cmake/CommonSettings.cmake) +set_common_settings() + +find_package(Boost REQUIRED COMPONENTS asio) + + +if(WIN32) + set(SOURCES src/restart_handler_win.cpp) +else() + set(SOURCES src/restart_handler_unix.cpp) +endif() + +add_library(RestartHandler ${SOURCES}) +target_include_directories(RestartHandler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(RestartHandler PUBLIC Boost::asio CommandEntry PRIVATE Logger) + +include(../../cmake/ConfigureTarget.cmake) +configure_target(RestartHandler) + +# if(BUILD_TESTS) +# enable_testing() +# add_subdirectory(tests) +# endif() diff --git a/src/agent/restart_handler/include/restart_handler.hpp b/src/agent/restart_handler/include/restart_handler.hpp new file mode 100644 index 0000000000..b18e6058da --- /dev/null +++ b/src/agent/restart_handler/include/restart_handler.hpp @@ -0,0 +1,35 @@ +#pragma once +#include + +#include + +#include + +namespace restart_handler +{ + /// @brief Class for handling service restarts. + class RestartHandler + { + public: + /// @brief Command-line arguments for the service. + static std::vector cmd_line; + explicit RestartHandler(); + + /// @brief Stores the command-line arguments passed to the agent. + /// @param argc Number of arguments. + /// @param argv Array of arguments. + static void SetCommandLineArguments(int argc, char* argv[]) + { + for (int i = 0; i < argc; ++i) + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + RestartHandler::cmd_line.emplace_back(argv[i]); + } + RestartHandler::cmd_line.emplace_back(nullptr); + } + + /// @brief Executes the restart command. + /// @return Result of the restart command execution. + static boost::asio::awaitable RestartCommand(); + }; +} // namespace restart_handler diff --git a/src/agent/restart_handler/src/restart_handler_unix.cpp b/src/agent/restart_handler/src/restart_handler_unix.cpp new file mode 100644 index 0000000000..0d0de6db8d --- /dev/null +++ b/src/agent/restart_handler/src/restart_handler_unix.cpp @@ -0,0 +1,100 @@ +#include "restart_handler_unix.hpp" +#include +#include + +#include +#include + +namespace restart_handler +{ + + std::vector RestartHandler::cmd_line; + + bool UsingSystemctl() + { + return (0 == std::system("which systemctl > /dev/null 2>&1") && nullptr != std::getenv("INVOCATION_ID")); + } + + boost::asio::awaitable RestartWithSystemd() + { + LogInfo("Systemctl restarting wazuh agent service."); + if (std::system("systemctl restart wazuh-agent") != 0) + { + co_return module_command::CommandExecutionResult {module_command::Status::IN_PROGRESS, + "Systemctl restart execution"}; + } + else + { + LogError("Failed using systemctl."); + co_return module_command::CommandExecutionResult {module_command::Status::FAILURE, + "Systemctl restart failed"}; + } + } + + void StopAgent() + { + const int timeout = 30; + time_t start_time = time(nullptr); // Record the start time to track the timeout duration + + pid_t pid = getppid(); + + // Shutdown Gracefully + kill(pid, SIGTERM); + + while (true) + { + if (kill(pid, 0) != 0) + { + LogInfo("Agent gracefully stopped."); + break; + } + + if (difftime(time(nullptr), start_time) > timeout) + { + LogError("Timeout reached! Forcing agent process termination."); + kill(pid, SIGKILL); + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + + boost::asio::awaitable RestartWithFork() + { + pid_t pid = fork(); + + if (pid < 0) + { + LogError("Fork failed."); + } + else if (pid == 0) + { + // Child process + StopAgent(); + + LogInfo("Starting wazuh agent in a new process."); + + if (execve(RestartHandler::cmd_line[0], RestartHandler::cmd_line.data(), nullptr) == -1) + { + LogError("Failed to spawn new Wazuh agent process."); + } + } + + co_return module_command::CommandExecutionResult {module_command::Status::IN_PROGRESS, + "Pending restart execution"}; + } + + boost::asio::awaitable RestartHandler::RestartCommand() + { + + if (UsingSystemctl()) + { + return RestartWithSystemd(); + } + else + { + return RestartWithFork(); + } + } + +} // namespace restart_handler diff --git a/src/agent/restart_handler/src/restart_handler_unix.hpp b/src/agent/restart_handler/src/restart_handler_unix.hpp new file mode 100644 index 0000000000..2d8c05f961 --- /dev/null +++ b/src/agent/restart_handler/src/restart_handler_unix.hpp @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include + +namespace restart_handler +{ + + /// @brief Checks if systemctl is available and running as a systemd service. + /// + /// Determines if systemctl can be used in the current environment. + /// @return true if systemctl is available and running as a systemd service, otherwise returns false. + bool UsingSystemctl(); + + /// @brief Stops the agent by terminating the child process. + /// + /// This function sends a SIGTERM signal to the agent process to stop it. If the agent process + /// does not stop within a specified timeout period (30 seconds), it forces termination with a SIGKILL signal. + void StopAgent(); + + /// @brief Restarts the module by forking a new process. + /// + /// This function restarts the module by creating a new child process. + /// + /// @return A boost::asio::awaitable containing the result of the command execution. + boost::asio::awaitable RestartWithFork(); + + /// @brief Restarts the module using systemd service management. + /// + /// This function restarts the module via systemd, ensuring the module is properly restarted using + /// system service management mechanisms. + /// + /// @return A boost::asio::awaitable containing the result of the command execution. + boost::asio::awaitable RestartWithSystemd(); + +} // namespace restart_handler diff --git a/src/agent/restart_handler/src/restart_handler_win.cpp b/src/agent/restart_handler/src/restart_handler_win.cpp new file mode 100644 index 0000000000..bffb6155bb --- /dev/null +++ b/src/agent/restart_handler/src/restart_handler_win.cpp @@ -0,0 +1,17 @@ +#include +#include +#include + +namespace restart_handler +{ + + std::vector RestartHandler::cmd_line; + + boost::asio::awaitable RestartHandler::RestartCommand() + { + // TODO + co_return module_command::CommandExecutionResult {module_command::Status::FAILURE, + "RestartHandler is not implemented yet"}; + } + +} // namespace restart_handler diff --git a/src/agent/service/wazuh-agent.service b/src/agent/service/wazuh-agent.service index e6a65bf449..ed696f62fc 100644 --- a/src/agent/service/wazuh-agent.service +++ b/src/agent/service/wazuh-agent.service @@ -7,13 +7,10 @@ After=network.target network-online.target Type=simple ExecStart=/usr/bin/env WAZUH_HOME/wazuh-agent +TimeoutStopSec=30s KillSignal=SIGTERM -KillMode=process - -SendSIGKILL=no - RemainAfterExit=yes [Install] diff --git a/src/agent/src/agent.cpp b/src/agent/src/agent.cpp index e28a8523a0..ef235f1a93 100644 --- a/src/agent/src/agent.cpp +++ b/src/agent/src/agent.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -152,6 +153,11 @@ void Agent::Run() }, m_messageQueue); } + else if (cmd.Module == module_command::RESTART_MODULE) + { + LogInfo("Restart: Initiating restart"); + return restart_handler::RestartHandler::RestartCommand(); + } return DispatchCommand(cmd, m_moduleManager.GetModule(cmd.Module), m_messageQueue); }), "CommandsProcessing"); diff --git a/src/agent/src/main.cpp b/src/agent/src/main.cpp index 2127599e7a..59a1c2ed1a 100644 --- a/src/agent/src/main.cpp +++ b/src/agent/src/main.cpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace program_options = boost::program_options; @@ -94,6 +95,7 @@ int main(int argc, char* argv[]) } else { + restart_handler::RestartHandler::SetCommandLineArguments(argc, argv); StartAgent(validOptions.count(OPT_CONFIG_FILE) ? validOptions[OPT_CONFIG_FILE].as() : ""); } diff --git a/src/agent/src/unix/instance_handler_unix.cpp b/src/agent/src/unix/instance_handler_unix.cpp index 3c5e49e358..4f6f1584f7 100644 --- a/src/agent/src/unix/instance_handler_unix.cpp +++ b/src/agent/src/unix/instance_handler_unix.cpp @@ -80,7 +80,7 @@ namespace instance_handler const std::string filename = fmt::format("{}/wazuh-agent.lock", m_lockFilePath); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg, cppcoreguidelines-avoid-magic-numbers) - int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (fd == -1) { m_errno = errno;