Closed gdiazlo closed 1 week ago
(11/06/2024) Understanding the issue requeriments. Reviewing related issues, discussing with team issue impact and doubts to deliver to management. (17/06/2024) Understanding new protocol POC to start design. A list of questions has been created to clarify tasks. (18/06/2024) Researching implementation language capabilities, still understanding the data storage and the functionalities and limitations it has to handle, researching about nlohmann json and its availability to cover our case to manage the messages straightforwardly. (19/06/2024) We have been discussing the list of messages this new module should be able to manage and as far as we have researched found the minimum commands are as follows:
Main commands:
Optional/not definitive commands
A primitive approach for the JSON structure representing the commands could be as follows:
command = {
"origin": { // Data regarding manager requester.
"name": "node01", // Name or manager ID.
"module": "upgrade_module" // Manager operation that triggers the command request
},
"command": "upgrade_update_status", // ID for the operation to be performed in the agent side
"parameters": { // Parameters regarding the operation
...
...
},
"status": "pending" // Command dispatch status
}
Currently designing sequence diagram diagram to illustrate it.
(20/06/2024) The attached diagram wants to replicate the following behavior:
When a command is received, a timestamp is generated, which can be used as a primary key or a part of it, and will be stored in a sqlite3 table. This table has 3 columns, timestamp representing the time at which the command is received, command, a JSON object that stores the information received and a status column, which will allow to track the status of the command.
This part of receiving the command has to be done by a method of the class, preferably running in a thread so that it constantly be listening for command arrivals and inserting them into the table. Each time an insertion is done, 1 will be added to a variable called pending_commands initialized to 0.
Another method running in another thread, has to be checking the pending commands variable, as long as it is greater than 0, it will make a selection in the table of the rows with pending status and the oldest timestamp. Once the row is obtained, the command is passed to the executor object that will execute the command, and in turn, the status of the selected row in the DB will be modified, and its status will change to processing.
The Executor, once it has completed the execution of a command, will change the status of the row that is processed from the SQL table table and set it to done.
(21/06/2024) Working on POC. (24/06/2024) Continued working on POC. Developing classes structure and some use cases. (25/06/2024) Writting document to present management implementation options, capabilities and limitations. (26/06/2024) Uploaded POC.
After some discussion with the team, we have decided to investigate the use of databases and text files for command storage.
I am analyzing the pros and cons of text files, I have written a small program that simulates this commander using those text files in a simplified form: commander.txt
Working with the design of classes, necessary methods and solving some doubts about the requirements.
The current list of possible necessary commands to be considered:
upgradeAgent
restartAgent
applyCentralizedConfiguration
getAgentConfiguration
enrollAgent
executeActiveResponse
Initial format of the JSON containing the command information:
{
"command": {
"name": "001",
"type": "stateless"
},
"origin": {
"serverName": "node01",
"moduleName": "upgradeModule"
},
"parameters": {
"extra_args": [],
"error": 0,
"data": "Upgrade Successful",
"status": "Done"
}
}
classDiagram
class Server {
+sendCommand(cmd: Command)
}
class CommandManager {
-commandStore: CommandStore
-executor: Executor
+storeCommands()
+executeCommand()
}
class CommandStore {
+storeCommand(cmd: Command)
+getNextCommand(): Command
+deleteCompletedCommands(cmd: Command)
}
class Executor {
-feedback: Feedback
+execute(cmd: Command)
+generateFeedback(cmd: Command)
+reportFeedback(): Feedback
}
class AgentCommsAPIClient {
+getConnection()
+pollCommands()
+receiveCommands(cmds: List<Command>)
+addCommands(cmds: List<Command>)
+sendFeedback(feedback: Feedback)
}
class Command {
+name: String
+type: String
+data: String
+status: String
+execute()
+markCompleted()
}
class Feedback {
+status: String
+message: String
}
CommandManager "1" *-- "1" CommandStore
CommandManager "1" *-- "1" Executor
CommandManager "1" -- "1" AgentCommsAPIClient
AgentCommsAPIClient "1..*" -- "1" Server
CommandStore "1" -- "1..*" Command
Command "1" -- "0..1" Feedback
Executor "1" -- "0..1" Feedback
sequenceDiagram
participant Server
participant AgentCommsAPIClient
participant CommandManager
participant CommandStore
participant Executor
participant Feedback
AgentCommsAPIClient->>Server: getConnection()
AgentCommsAPIClient->>Server: pollCommands()
Server->>AgentCommsAPIClient: sendCommand()
AgentCommsAPIClient->>CommandManager: addCommands()
CommandManager->>CommandStore: storeCommand()
CommandManager->>Executor: executeCommand()
Executor->>CommandStore: getNextCommand()
Executor->>Executor: execute()
Executor->>Feedback: generateFeedback()
Executor->>CommandManager: reportFeedback()
CommandManager->>CommandStore: markCompleted()
CommandStore->>CommandStore: deleteCompletedCommands()
CommandManager->>AgentCommsAPIClient: reportFeedback()
AgentCommsAPIClient->>Server: reportFeedback()
I have been working on a little in c++ to try to approach little by little to the requirements of the issue, in this case, what I have is a Commander class, that executes two threads (pending to analyze the use of routines and coroutines), one of them is in charge of receiving the messages and save them in the store, and the other to process them and pass the feedback, to then delete them from the store: https://github.com/wazuh/wazuh-agent/tree/agent-command-manager
File: commander/commander.cpp
Example of use in the restar-agent case:
root@ubuntu24:/vagrant# ./commander
Command queue loaded from file.
Enter command in JSON format (or type 'quit' to stop): {"command":{"name":"restart-agent","type":"command"},"origin":{"moduleName":"restart","serverName":"node01"},"parameters":{"data":"restart agent","error":0,"extra_args":[],"status":""}}
Command queue saved to file.
Command received and added to the queue.
root@ubuntu24:/vagrant# ./commander
Command queue loaded from file.
Enter command in JSON format (or type 'quit' to stop):
Processing command: {"command":{"name":"restart-agent","type":"command"},"origin":{"moduleName":"restart","serverName":"node01"},"parameters":{"data":"restart agent","error":0,"extra_args":[],"status":""},"status":"processing"}
Feedback: {"command":{"command":{"name":"restart-agent","type":"command"},"origin":{"moduleName":"restart","serverName":"node01"},"parameters":{"data":"restart agent","error":0,"extra_args":[],"status":""},"status":"processing"},"message":"Command processed successfully.","status":"completed"}
Command queue saved to file.
After a meeting with the team and Vikman, we have drawn some conclusions to improve the poc, I have updated the code to better contain the dispatch and execute functions: https://github.com/wazuh/wazuh-agent/blob/poc/4-agent-command-manager/poc/commander/commander.cpp
Doing some tests with the code flow we can observe the following:
{"command":{"name":"command2","type":"active-response"},"origin":{"moduleName":"module1","serverName":"node01"},"parameters":{"data":"","error":0,"extra_args":[],"status":"pending"}}
{"command":{"name":"command1","type":"restart-agent"},"origin":{"moduleName":"restart","serverName":"node01"},"parameters":{"data":"restart agent","error":0,"extra_args":[],"status":"pending"}}
Video testing demonstration, top cmd is program execution, and bottom cmd is for command_queue.txt, the file where the commands for persistence are stored:
https://github.com/wazuh/wazuh-agent/assets/60003131/8891e2b9-d455-46cd-a07c-74881e6ffdcb
LGTM. GJ team!
Parent issue:
Description
As part of the new data persistence model being implemented across Wazuh, we need a new way to manage commands sent from the Wazuh servers to the agents. In this spike, we will identify all the commands the agent must support, including the data required by the agent to execute them. We will also design a command manager which will be in charge of executing these commands.
The following diagram outlines a simplified design:
Functional requirements
Implementation restrictions
Plan