Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agent stateful modules redesign #2

Closed
Tracked by #22887
vikman90 opened this issue May 31, 2024 · 8 comments
Closed
Tracked by #22887

Agent stateful modules redesign #2

vikman90 opened this issue May 31, 2024 · 8 comments
Assignees
Labels

Comments

@vikman90
Copy link
Member

vikman90 commented May 31, 2024

Parent issue:

Description

In this spike we're going to write a program which will test the persistence model within the agent. We will transform FIM module into a library which will be used to detect file changes.

FIM will ensure the state generated is persisted, across program restarts. On a restart, differences between the saved state and new changes will be calculated locally, generating change events.

We will design how these events will reach the Wazuh servers in:

This will give us a vision about the current persistence capabilities as we want expand this model to the rest of the modules.

During this exploration we will investigate how this model could be applied to other modules such as SCA and Syscollector.

The following diagrams present an overall design for the agent, but these will change during the development of the spike.

Component diagram

C4Component
    title Agent components

    Boundary(agent, "Agent", "") {
        Component(logrotate, "Log rotate", "?", "Rotates the log")
        Component(cli, "CLI", "", "Command-line interface")
        Component(client, "Client", "", "HTTP 2.0 manager's client")
        Component(storage, "Queue", "", "Persistent message queue")
        Component(commander, "Commander", "", "Runs commands from manager")
    }

    Boundary(deps, "Dependencies", "") {
        Component(sqlite, "SQLite")
    }

    Boundary(modules, "Modules", "") {
        Component(executors, "Executors", "", "Upgrade, Active response...")
        Component(collectors, "Collectors", "", "Logcollector, FIM, SCA...")
    }

    Boundary(server, "Server", "") {
        Component(manager, "Manager", "", "Server")
    }

    Boundary(shared, "Shared components", "") {
        Component(dbsync, "DBsync", "", "State difference engine")
    }

    Rel(client, manager, "Connects")
    Rel(storage, sqlite, "Uses")
    Rel(collectors, storage, "Writes")
    Rel(client, storage, "Reads")
    Rel(client, commander, "Queries")
    Rel(commander, executors, "Executes")
    Rel(cli, commander, "Queries")
    Rel(collectors, dbsync, "Uses")
Loading

Data flow

sequenceDiagram
    participant Manager A
    participant Manager B
    participant Agent
    participant Queue
    participant FIM
    participant DBsync

    FIM->>DBsync: File A state
    activate DBsync
    DBsync-->>FIM: File A changed
    deactivate DBsync

    FIM->>Queue: message (/stateful, A)
    FIM->>Queue: message (/stateless, A)

    FIM->>DBsync: File B state
    activate DBsync
    DBsync-->>FIM: File B changed
    deactivate DBsync

    FIM->>Queue: message (/stateful, B)
    FIM->>Queue: message (/stateless, B)

    Agent->>Queue: poll (/stateless)
    Queue-->>Agent: {A, B}
    Agent->>Manager A: /stateless {A, B}
    activate Manager A

    Agent->>Queue: poll (/stateful)
    Queue-->>Agent: {A, B}
    Agent->>Manager B: /stateful {A, B}
    activate Manager B

    Manager A-->>Agent: 200 OK
    deactivate Manager A
    Agent->>Queue: delete (/stateless, {A, B})
   
    Manager B-->>Agent: 200 OK
    deactivate Manager B
    Agent->>Queue: delete (/stateful, {A, B})
Loading

Implementation Restrictions

  • Build Tool: Use CMake for building the project.
  • Programming Language: Implement all new code in C++.
  • Current Persistence Model: Leverage the existing persistence model where feasible.
  • State Difference Engine: Utilize DBsync for calculating state differences when applicable.

Plan

  1. Define Layout:

  2. FIM Module Transformation:

    • Convert the File Integrity Monitoring (FIM) module into a library.
    • Include necessary dependencies for the FIM library.
  3. Persistence/Storage System:

    • Import and integrate the persistence/storage system as a library.
  4. DBsync Integration:

    • Import DBsync and utilize it as the engine for state difference calculations.
    • Consider renaming DBsync if necessary.
  5. Proof of Concept (nice-to-have):

    • Develop a proof of concept that generates events across program restarts to validate the persistence model.
@vikman90 vikman90 added the level/task Task issue label May 31, 2024
@gdiazlo gdiazlo changed the title [draft] Spike - agent data persistence Spike - agent stateful modules redesign Jun 5, 2024
@gdiazlo gdiazlo added level/epic Epic issue type/enhancement Enhancement issue and removed level/epic Epic issue labels Jun 5, 2024
@cborla cborla self-assigned this Jun 6, 2024
@vikman90 vikman90 added the phase/spike Spike label Jun 7, 2024
@vikman90 vikman90 changed the title Spike - agent stateful modules redesign Agent stateful modules redesign Jun 7, 2024
@cborla
Copy link
Member

cborla commented Jun 8, 2024

Define layout (v1)

Possible directory structure (TBD)

fim_project/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt  
│   ├── fim/
│   │   ├── FIM.cpp
│   │   ├── FIM.h
│   │   └── ...
│   ├── dbsync/
│   │   ├── DBsync.cpp
│   │   ├── DBsync.h
│   │   └── ...
│   ├── persistence/
│   │   ├── Persistence.cpp
│   │   ├── Persistence.h
│   │   └── ...
│   └── main.cpp
├── include/
│   ├── fim/
│   │   ├── FIM.h
│   │   └── ...
│   ├── dbsync/
│   │   ├── DBsync.h
│   │   └── ...
│   └── persistence/
│       ├── Persistence.h
│       └── ...
├── tests/
│   ├── CMakeLists.txt  
│   ├── test_fim.cpp
│   ├── test_dbsync.cpp
│   └── test_persistence.cpp
└── docs/
    ├── design.md
    └── usage.md

Steps to Define the Layout

  • Create the Repository (fim_project).
  • Setup CMake.
  • Organize Source Code.
  • Import and Refactor FIM Module.
  • Integrate DBsync and Persistence.
  • Main Application Entry Point.
  • Add Unit Tests (InitGoogleTest).
  • Documentation.

Possible CMake

  • Root CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

project(FIM_Project)

set(CMAKE_CXX_STANDARD 17)

# Add subdirectories
add_subdirectory(src)
add_subdirectory(tests)

# Include directories
include_directories(include)
  • src/CMakeLists.txt
# Add the FIM library
add_library(fim STATIC
    fim/FIM.cpp
    fim/FIM.h
    # Add other FIM source files if needed
)

# Add the DBsync library
add_library(dbsync STATIC
    dbsync/DBsync.cpp
    dbsync/DBsync.h
    # Add other DBsync source files if needed
)

# Add the Persistence library
add_library(persistence STATIC
    persistence/Persistence.cpp
    persistence/Persistence.h
    # Add other Persistence source files if needed
)

# Add include directories for the libraries
target_include_directories(fim PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/fim)
target_include_directories(dbsync PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/dbsync)
target_include_directories(persistence PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/persistence)

# Create the main executable
add_executable(main main.cpp)

# Link the libraries to the main executable
target_link_libraries(main fim dbsync persistence)
  • tests/CMakeLists.txt
# Find the GTest package
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

# Add test executables
add_executable(test_fim test_fim.cpp)
target_link_libraries(test_fim ${GTEST_LIBRARIES} pthread fim)

add_executable(test_dbsync test_dbsync.cpp)
target_link_libraries(test_dbsync ${GTEST_LIBRARIES} pthread dbsync)

add_executable(test_persistence test_persistence.cpp)
target_link_libraries(test_persistence ${GTEST_LIBRARIES} pthread persistence)

# Enable testing
enable_testing()

# Add tests
add_test(NAME test_fim COMMAND test_fim)
add_test(NAME test_dbsync COMMAND test_dbsync)
add_test(NAME test_persistence COMMAND test_persistence)

Approval Section

The team must review and approve the proposed design, or iterate with changes until the final design is approved.

  • Is the design approved by the team? 🔴
    • No, we decided to improve it.

@cborla
Copy link
Member

cborla commented Jun 11, 2024

Define layout (v2)

After analyzing the previous proposal with the team, we decided to stick to the structure defined for the wazuh-agent project. Making some small additions in this branch only for the present spike.

Steps to Define the Layout

  • Use wazuh-agent repository (fim).
  • Setup CMake.
  • Organize Source Code.
  • Import and Refactor FIM Module.
  • Integrate DBsync and Persistence.
  • Main Application Entry Point.
  • Add Unit Tests (InitGoogleTest).
  • Documentation.
wazuh-agent/
├── CMakeLists.txt
├── modules/
│   ├── fim/ (former syscheck)
│   │   ├── CMakeLists.txt
│   │   ├── main.cpp
│   │   ├── include/
│   │   │   ├── FIM.h
│   │   │   └── ...
│   │   ├── src/
│   │   │   ├── FIM.cpp
│   │   │   ├── FIM.h
│   │   │   └── ...
│   │   └── tests/
│   │       ├── CMakeLists.txt
│   │       └── test_fim.cpp
│   └── [additional modules...]
├── common/
│   ├── dbsync/
│   └── [additional modules...]
└── build/
    └── [build output...]

Possible CMake

  • Root CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

project(WazuhAgent)

set(CMAKE_CXX_STANDARD 17)

# Add subdirectories
add_subdirectory(modules/fim)
add_subdirectory(common/dbsync)

# Enable testing
enable_testing()

# Add subdirectories for tests
add_subdirectory(modules/fim/tests)
add_subdirectory(common/dbsync/tests)

# Include directories
include_directories(modules/fim/include)
include_directories(common/dbsync/include)
  • modules/fim/CMakeLists.txt
# Add the FIM library
add_library(fim STATIC
    src/FIM.cpp
    include/FIM.h
    # Add other FIM source files if needed
)

# Add include directories for the FIM library
target_include_directories(fim PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

# Create the main executable
add_executable(fim_main main.cpp)

# Link the libraries to the main executable
target_link_libraries(fim_main fim dbsync)

  • modules/fim/tests/CMakeLists.txt
# Find the GTest package
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

# Add test executables
add_executable(test_fim test_fim.cpp)
target_link_libraries(test_fim ${GTEST_LIBRARIES} pthread fim)

# Enable testing
enable_testing()

# Add tests
add_test(NAME test_fim COMMAND test_fim)

  • common/dbsync/CMakeLists.txt
# Add the DBsync library
add_library(dbsync STATIC
    src/DBsync.cpp
    include/DBsync.h
    # Add other DBsync source files if needed
)

# Add include directories for the DBsync library
target_include_directories(dbsync PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

@LucioDonda
Copy link
Member

Some basic steps needed after a quick catch-up with the issue (WIP) :

  1. Create the directory structure (as stated in previous comment)
  2. Move all the syscheck directory to where we defined the fim directory:
├── syscheckd
│   ├── build
│   ├── CMakeLists.txt
│   ├── coverage_report
│   ├── include
│   └── src
* build
* CMakeLists.txt -> leave
* coverage_report -> should we maintain it? 
* include -> remains as include
* src -> remains as src
  1. Unifiy all the tests src/unit_tests/syscheckd to fim/tests same for /syscheckd/src/db/tests/
  • Is there any other test related to syscheck we need to add?
  1. Replace in Cmake the output of non-windows systems with something like::
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    add_library(wazuh-syscheckd STATIC ${SYSCHECKD_SRC})
    add_library(wazuh-syscheckd-event STATIC ${SYSCHECKD_SRC})
    target_compile_definitions(wazuh-syscheckd PUBLIC -D_WIN32_WINNT=0x600)
    target_compile_definitions(wazuh-syscheckd-event PUBLIC -D_WIN32_WINNT=0x600 -DEVENTCHANNEL_SUPPORT)
else()
    # Create a shared library
    add_library(wazuh-syscheckd SHARED ${SYSCHECKD_SRC})

    # Optionally, create a static library as well
    add_library(wazuh-syscheckd-static STATIC ${SYSCHECKD_SRC})
endif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
  1. Move main.c functionality to cpp:
  • Which module will be resonsible of instantiating this new class?

@LucioDonda
Copy link
Member

LucioDonda commented Jun 13, 2024

FIM Module Transformation :

Convert the File Integrity Monitoring (FIM) module into a library:

  • Methods or functions needed:

    • fim_initialize / fim_db_teardown (from syscheckd.c)
    • dbsync_initialize (dbsync.cpp)
    • rsync_initialize (rsync.cpp)
    • read XML configuration files -> library ?
    • Parse XML -> Will this be done by each module?
    • rootcheck_init / rootcheck_connect (rootcheck.c) -> To be deprecated ?
  • Communication:

    • This module should start the socket queue, should this remain like this?
    • What will be the message format?
    • Should we consider additional IPCs?
  • Basic process:

  1. Start / initialize DBSync.
  2. Start communication (or at least this side of the socket).
  3. Read the configuration for the.
  4. Print and / or check directories to monitor (ignores, realtime, regex, etc.).
  5. Start / initialize File Integrity Monitoring (itself as it's being done right now).
  6. Realtime scan start.
  7. Start Daemon:
    1. fim_scan();
    2. fim_run_integrity();
    3. fim_whodata_initialize();

Include necessary dependencies for the FIM library:

  • Wazuh:

    • DBsync
  • External:

    • Sqlite (from old parts of the code).

    • cJson (from old parts of the code).

    • nlohmann (from old parts of the code).

    • openssl (from old parts of the code).

    • bzip2 (from old parts of the code). -> DOuble check!

    • Multithreading:
      std::thread (from C++11)

    • Directory handling:
      std::filesystem (C++17)

    • Configuration reader / parser:

    • Regex:

@cborla
Copy link
Member

cborla commented Jun 14, 2024

Define layout (v3)

After analyzing the layout v2 with the team, @LucioDonda's proposal here, and the redefinition layout here. We determined that the new structure will be as follows:

wazuh-agent/
├── src/
│   ├── CMakeLists.txt
│   ├── modules/
│   │   ├── fim/ (former syscheck)
│   │   │   ├── CMakeLists.txt
│   │   │   ├── build/
│   │   │   │   └── [build output...]
│   │   │   ├── main.cpp
│   │   │   ├── include/
│   │   │   │   ├── fim.h
│   │   │   │   └── ...
│   │   │   ├── src/
│   │   │   │   ├── fim.cpp
│   │   │   │   ├── fim.h
│   │   │   │   └── ...
│   │   │   └── tests/
│   │   │       ├── CMakeLists.txt
│   │   │       └── test_fim.cpp
│   │   └── [additional modules...]
│   ├── common/
│   │   ├── dbsync/
│   │   └── [additional modules...]
│   └── build/
│       └── [build output...]
├── etc/
│   ├── config/
│   ├── selinux/
│   └── ruleset/
│       ├── sca/
│       └── rootcheck/
├── packages/
│   └── installers/
│       ├── unix/ (former init folder, including upgrade.sh and install.sh)
│       └── win32/
└── bump-version.sh    

Branch: 2-agent-stateful-modules-redesign

@nbertoldo
Copy link
Member

DBsync Integration

Import DBsync and utilize it as the engine for state difference calculations. Below is the proposed sequence diagram:

sequenceDiagram
    participant Agent as Agent
    participant Queue as Queue
    participant Fim as FIM
    participant OS as os
    participant Dbsync as dbsync
    participant FimDB as fim.db


    loop scan (each frequency seconds)
        loop each directory
            Fim ->> OS: get data
            OS -->> Fim: info
            Fim ->> Dbsync: File state
            Dbsync ->> FimDB: Update DB
            FimDB -->> Dbsync: 
            Dbsync -->> Fim: File state changed
            Fim -->> Queue: message (/stateful)
            Agent ->> Queue: poll (/stateful)
            Queue -->> Agent: event (/stateful)
            Fim -->> Queue: message (/stateless)
            Agent ->> Queue: poll (/stateless)
            Queue -->> Agent: event (/stateless)
        end
    end
Loading

As for the persistence of the FIM database, it is necessary to create an instance of DBSync in 'persistent' mode: Giving DBSync the capability of DB persistance
Currently, Dbsync is initialized in 'volatile' mode recreating the DB every time the module is started.

/**
 * @brief Creates a new DBSync instance (wrapper)
 *
 * @param host_type          Dynamic library host type to be used.
 * @param db_type            Database type to be used (currently only supported SQLITE3)
 * @param path               Path where the local database will be created.
 * @param sql_statement      SQL sentence to create tables in a SQL engine.
 * @param upgrade_statements SQL sentences to upgrade tables in a SQL engine.
 *
 * @note db_management will be DbManagement::PERSISTENT as default.
 *
 * @return Handle instance to be used for common sql operations (cannot be used by more than 1 thread).
 */
EXPORTED DBSYNC_HANDLE dbsync_create_persistent(const HostType      host_type,
                                                const DbEngineType  db_type,
                                                const char*         path,
                                                const char*         sql_statement,
                                                const char**        upgrade_statements);

@cborla
Copy link
Member

cborla commented Jun 15, 2024

3. Persistence/Storage System (Queue)

PoC Objective

Develop a simple test that meets the following points.

  • Multiple instances: Sort stateful, stateless and commands messages.
  • Storage: Output from FIM, Logcollector, Inventory, etc, will end up in this component.
  • Persistence: Whatever is in this memory must remain even if the agent is disconnected or shut down.
  • Limitation: We will do it at the input. If the limit is reached, we will leave the Insert operation blocked until there is space.
  • Reading: The consumer (connection to the server) will be able to take up to N messages from this queue, depending on the type (stateless/stateful/command). This is why I say that, possibly, there are several instances. They are not deleted from the queue, it is a simple poll.
  • Management: when the client has sent the messages and has the ACK, then it will remove them from the queue.

Code layout

└── queue
    ├── build
    ├── CMakeLists.txt
    ├── include
    │   └── json.hpp
    ├── main.cpp
    ├── queue.cpp
    ├── queue.hpp
    └── sealed_bucket_persistence.json

Test Description

The purpose of this test is to evaluate the functionality and performance of the Queue system, which manages messages with a fixed capacity and ensures persistence. The test involves multiple producer and consumer threads handling different types of messages (STATEFUL and STATELESS).

Test Setup:

  • Producers: Two producer threads, each responsible for generating 50 and 20 messages of their respective types (STATEFUL and STATELESS). Producers insert messages into the SealedBucket (Queue) with a brief delay to simulate real-world message generation.
  • Consumers: Two consumer threads, each polling and processing messages of their respective types. Consumers retrieve up to 5 messages at a time and process them with a delay, simulating real-world message handling.

Code

#include "queue.hpp"
#include <iostream>
#include <thread>
#include <chrono>

/* initialization sets a limit of 10 messages for the bucket, ensuring that the queue will block inserts if this limit is reached */
#define SEALED_BUCKET_MAX 30

#define NUMBER_OF_STATEFUL_MSG_TO_GENERATE 50
#define NUMBER_OF_STATELESS_MSG_TO_GENERATE 20

void stateful_producer(SealedBucket& bucket) {
    for (int i = 0; i < NUMBER_OF_STATEFUL_MSG_TO_GENERATE; ++i) {
        Message msg{MessageType::STATEFUL, "Stateful message " + std::to_string(i)};
        bucket.insert(msg);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void stateless_producer(SealedBucket& bucket) {
    for (int i = 0; i < NUMBER_OF_STATELESS_MSG_TO_GENERATE; ++i) {
        Message msg{MessageType::STATELESS, "Stateless message " + std::to_string(i)};
        bucket.insert(msg);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void stateful_consumer(SealedBucket& bucket) {
    std::this_thread::sleep_for(std::chrono::seconds(1)); // Wait for some messages to be produced

    while (true) {
        auto messages = bucket.poll(MessageType::STATEFUL, 5);
        if (messages.empty()) {
            break;
        }

        for (const auto& msg : messages) {
            std::cout << "Consumer 1, Polled: " << msg.content << std::endl;
        }

        bucket.acknowledge(messages);
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
}

void stateless_consumer(SealedBucket& bucket) {
    std::this_thread::sleep_for(std::chrono::seconds(1)); // Wait for some messages to be produced

    while (true) {

        auto messages = bucket.poll(MessageType::STATELESS, 5);
        if (messages.empty()) {
            break;
        }

        for (const auto& msg : messages) {
            std::cout << "Consumer 2, polled: " << msg.content << std::endl;
        }

        bucket.acknowledge(messages);
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
}
int main() {
    SealedBucket bucket(SEALED_BUCKET_MAX);

    std::thread stateful_prod_thread(stateful_producer, std::ref(bucket));
    std::thread stateless_prod_thread(stateless_producer, std::ref(bucket));
    std::thread stateful_cons_thread(stateful_consumer, std::ref(bucket));
    std::thread stateless_cons_thread(stateless_consumer, std::ref(bucket));

    stateful_prod_thread.join();
    stateless_prod_thread.join();
    stateful_cons_thread.join();
    stateless_cons_thread.join();

    return 0;
}

Sample Output:

Consumer 1, Polled: Stateful message 0
Consumer 2, Polled: Stateless message 0

This test ensures that the SealedBucket handles concurrent access, maintains persistence, and properly blocks when full, providing a robust solution for message queue management.

How to test it

  • Checkout to branch
git checkout 2-agent-stateful-modules-redesign
  • Compile the Project
cd wazuh-agent/src/common/queue/build
cmake ..
make
  • Run the test
./sealed_bucket

@LucioDonda
Copy link
Member

LucioDonda commented Jun 17, 2024

FIM Module Transformation proccess to follow(WIP):

  • Replace main.c and syscheck.c with a cpp class (we may need a singleton here). Internally it will have the ability to call different parts of the already used C code (create_db.c fim_diff_changes.c run_check.c run_realtime.c )
    This approach may be the simplest one and the less time-consuming (and perhaps the most secure one, because by reusing most of the old code we may have the chance to already fixed issues) but it won't be easy to improve in future releases and it won't be scalable.

  • Unify initilization for windows and UNIX:

Pseudo-code

int start_fim()
{

    // Options and configurations
#ifndef WIN32
        /* Set the name *
        /* Change current working directory */
        /* Check if the group given is valid */
        /* Privilege separation */
#endif

    /* Initialize error logging for shared modulesd dbsync */

    // Options and configurations
        /* Read internal options */
        /* Check if the configuration is present */
        /* Read syscheck config */
        /* Rootcheck config */

#ifdef USE_MAGIC
    /* Setup libmagic */ --> is this still neccesary
#endif

    // Communication
#ifndef WIN32
        /* Start signal handling */
        // Start com request thread
        /* Create pid */
        /* Connect to the queue */
        /* Start up message */
#else
        /* Start up message */
#endif

    // Print Information
        /* Print directories to be monitored, ignores, sregex ignores, files with no diff. */
        /* WIN  registry ignores*/

    /* Check directories set for real time */

#ifndef WIN32
    if (start_realtime == 1) {
        realtime_start();
    }

    // Audit events thread -> whodata
#else
    foreach(syscheck.directories) {
        if (dir_it->options & REALTIME_ACTIVE) {
            realtime_start(); 
        }
    }
#endif

    fim_initialize();

    start_daemon();

    return (0);
}
  • Separate and if possible rename the rest of the functions and files:
    Right now there are more than 80 functions in syscheck.h all of them from create_db.c run_realtime.c syscheck_audit.c run_check.c audit_parse.c audit_rule_handling.c win_whodata.c fim_diff_changes.c config.c syscom.c audit_healthcheck.c

functions per file on syscheck h

The simplest approach would be to move all the declarations to their respective headers and include those in the syscheck.h. This can also be used to improve the documentation.

  • Design an interface that, in this case, will be instantiated as fim_db, but would also be helpful for SCA and syscollector. Right now this responds as something like:
Details

classDiagram
    class wmodule~ConfigType~ {
        <<Template>>
        -thread: std::thread
        -context: wm_context*
        -data: ConfigType*
        +wmodule(ctx: wm_context&, d: ConfigType*)
        ~wmodule()
    }

    class wm_context {
        -name: std::string
        -start: std::function~void()
        -destroy: std::function~void(ConfigType*)
        -dump: std::function~nlohmann::json(const ConfigType*)
        -sync: std::function~int(const std::string&)
        -stop: std::function~void(ConfigType*)
        -query: std::function~void(ConfigType*, nlohmann::json&)
    }

    wmodule *-- wm_context

    class SysCollectorConfig {
        <<ConfigType>>
        - diff
        - directories
        - disabled
        - ignore
        - max_eps
        - max_files_per_second
        - registry_ignore
        - scan_x
        - skip_x
        - synchronization
        - whodata
        - windows_registry
    }

    class SysCollector {
        - wm_sys_main()
        - wm_sys_destroy(void*)
        - wm_sys_dump(const void*): nlohmann::json
        - wm_sync_message(const std::string&): int
        - wm_sys_stop(void*)
        - wm_sys_query(void*, nlohmann::json&)
    }

    SysCollector ..> SysCollectorConfig : uses
    SysCollector ..> wmodule~SysCollectorConfig~ : instantiates

    note for SysCollector "Example instantiation\nof wmodule with\nSysCollectorConfig"

Loading

In this section the basic idea would be to:

  • replace rsync with a different callback that sends the respective stateful message. This will impact db.cpp mainly.
  • Enable the persistence in DBSYnc: The base case it's already developed, it will need some modifications in the calls as stated here and may be in checking some SQL statements.
  • Regarding the interface it can be implemented with a template, based on the C modules already used on wazuh this can be defined as:
classDiagram
    class wmodule~ConfigType~ {
        <<Template>>
        -thread: std::thread
        -context: wm_context*
        -data: ConfigType*
        +wmodule(ctx: wm_context&, d: ConfigType*)
        ~wmodule()
    }

    class wm_context {
        -name: std::string
        -start: std::function~void()~
        -destroy: std::function~void(ConfigType*)~
        -dump: std::function~nlohmann::json(const ConfigType*)~
        -sync: std::function~int(const std::string&)~
        -stop: std::function~void(ConfigType*)~
        -query: std::function~void(ConfigType*, nlohmann::json&)~
    }

    wmodule *-- wm_context

    class SysCollectorConfig {
        <<ConfigType>>
        # Configuration data members
    }

    class SysCollector {
        <<Instantiation>>
        - wm_sys_main()
        - wm_sys_destroy(void*)
        - wm_sys_dump(const void*): nlohmann::json
        - wm_sync_message(const std::string&): int
        - wm_sys_stop(void*)
        - wm_sys_query(void*, nlohmann::json&)
    }

    SysCollector ..> SysCollectorConfig : uses
    SysCollector ..> wmodule~SysCollectorConfig~ : instantiates

    note for SysCollector "Example instantiation\nof wmodule with\nSysCollectorConfig"

Loading

And a basic code for this would look like:

#include <functional>
#include <nlohmann/json.hpp>
#include <string>
#include <thread>

// Module context and main module structure
template <typename ConfigType>
class wmodule {
public:
    // Module context
    struct wm_context {
        std::string name;                           // Name for module
        std::function<void()> start;                // Main function
        std::function<nlohmann::json(const ConfigType*)> dump; // Dump current configuration
        std::function<int(const std::string&)> sync;       // Sync
        std::function<void(ConfigType*)> stop;      // Module destructor
        std::function<void(ConfigType*, nlohmann::json&)> query; // Run a query
    };

    std::thread thread;                 // Thread
    const wm_context* context;          // Context (common structure)
    ConfigType* data;                   // Data (module-dependent structure)

    // Constructor
    wmodule(const wm_context& ctx, ConfigType* d)
        : context(&ctx), data(d) {}

    // Destructor
    ~wmodule() {
        context->destroy(data);
    }
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Done
Development

No branches or pull requests

5 participants