wahl-sec / trident

Trident is an asynchronous environment monitor with plugin support and data collection.
https://wahl-sec.github.io/trident/
GNU General Public License v3.0
1 stars 0 forks source link
asynchronous daemon python python3

Trident

Description

Trident is a asynchronous monitor environment that allows for defining and running plugins to scan/modify/store resources on the system. Trident comes pre-packaged with some plugins to function as a monitor on the system.

Trident currently allows for modules written in Python to act as plugins using the Trident library to allow for some basic instructions.

Trident tries to use as little external modules as possible and allows the user to run Trident using only the standard library. This is done to ensure maximum compatability with hosts and to allow to focus on developing asynchronous libraries to use in the plugins.

For usage instructions on Trident please refer to the usage section.

Setup

Requirements

Trident is tested using the versions below but might work with earlier versions as well

Development Requirements

Instructions

Installing Trident is done using any of the following options:

Plugins

Trident uses plugins written in Python to act as a system monitor. The plugins need to be written in a specific format that allows Trident to control them from the daemon, this format is specified under section Developing Plugins.

By default the plugins are written to use non-blocking operations and uses yield to return a generator for the plugins which allows the plugins to do operation on each result without waiting for full completion of each plugin. It is possible to just use a return statement instead of yield to wait for full execution of the plugin.

If there are any results from the plugin and these are returned to the Trident runner then Trident is able to store the results in the data stores specified by the user. By default these data stores are stored in the .json format. If the plugin is using generators to return results iteratively the result for each iteration will be stored in the data store.

Usage

Examples

Arguments

The arguments for Trident can also be displayed using the -h, --help flag.

Required

Trident Configuration

Plugin Configuration

Storage Configuration

Checkpoint Configuration

Configuration

Configuration of Trident is made through the configuration file, by default located at config/trident.json.

Any argument that also exist as a possible configuration value in the config file will be used over the value in the configuration file for all the plugins, so the arguments exist as a sort of override to any configuration value.

The configuration file is divided into sections with the default section TRIDENT being used and the expected format should be according to the following example for configuring one runner.

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "[plugin_id]": {
                "path": "[plugin_path]",
                "plugin_args": {
                    "[arg_name]": "[arg_value]"
                },
                "args": {
                    "[runner_args]": {
                        ...
                    }
                }
            }
        }
    }
}

The following is a example configuration for two plugins, where the first runner instance don't store any of the produced results in a .json store.

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "plugin0": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 0
                },
                "args": {
                    "store": {
                        "no_store": true
                    }
                }
            },
            "plugin1": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 10
                },
                "args": {
                    "store": {
                        "no_store": false
                    }
                }
            }
        }
    }
}

The args section is optional and contain the following sections: daemon, store, runner, notification and trident.

The args section can be placed either inside each plugin definition to define specific behavior for that plugin or it can be placed outside to be defined for all of the plugins. These can also be combined to provide a general template for the plugins and specializations for certain plugins.

Example: This example shows two plugins were the global args is defined to not store any values produced by the plugins in stores on the system and at maximum use 2 concurrent workers. However, we have also defined plugin0 to store the results produced from the plugin and store them in the folder data/data.

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "plugin0": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 0
                },
                "args": {
                    "store": {
                        "no_store": false,
                        "path_store": "data/data"
                    }
                }
            },
            "plugin1": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 10
                }
            }
        },
        "args": {
            "store": {
                "no_store": true
            },
            "daemon": {
                "workers": 2
            }
        }
    }
}

The daemon and trident sections are applied as arguments for all of the plugins as they concern the general execution of the program.

The daemon section allows for the following arguments:

Example:

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "[plugin_id]": {
                "path": "[plugin_path]",
                "plugin_args": {
                    "[arg_name]": "[arg_value]"
                },
                "args": {
                    "[runner_args]": {
                        ...
                    }
                }
            }
        },
        "args": {
            "daemon": {
                "workers": 5
            }
        }
    }
}

The trident section allows for the following arguments:

Example: Disables the output for all plugins.

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "[plugin_id]": {
                "path": "[plugin_path]",
                "plugin_args": {
                    "[arg_name]": "[arg_value]"
                },
                "args": {
                    "[runner_args]": {
                        ...
                    }
                }
            }
        },
        "args": {
            "trident": {
                "quiet": true
            }
        }
    }
}

The store section allows for the following arguments:

Example: Store values for all plugins in a global store except one runner that does not store any values.

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "plugin0": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 0
                },
                "args": {
                    "store": {
                        "no_store": true
                    }
                }
            },
            "plugin1": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 10
                }
            },
            "plugin2": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 20
                }
            }
        },
        "args": {
            "store": {
                "global_store": "data/global.json"
            },
            "daemon": {
                "workers": 3
            }
        }
    }
}

The checkpoint section allows for the following arguments:

The notification section allows for the following arguments for HTTP notifications.

Example: A HTTP notification named http-notification including the results from the plugin.

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "plugin0": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 0
                },
                "args": {
                    "notification": {
                        "http-notification": {
                            "HTTP": {
                                "method": "GET",
                                "destination": "http://example.com",
                                "include_results": true
                            }
                        }
                    }
                }
            }
        }
    }
}

The notification section allows for the following arguments for E-Mail notifications.

Example: An e-mail notification named email-notification including the results from the plugin.

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "plugin0": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 0
                },
                "args": {
                    "notification": {
                        "email-notification": {
                            "EMAIL": {
                                "smtp_server": "host:port",
                                "sender": "trident@trident.com",
                                "receivers": ["receiver@example.com"],
                                "subject": "Test Subject",
                                "include_result": true
                            }
                        }
                    }
                }
            }
        }
    }
}

The runner section allows for the following arguments:

Example: Two plugins were the values of one of the plugins are stored if the runner encounters an exception and if the values match any of the filters [a-z] or [A-Z].

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "plugin0": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 0
                },
                "args": {
                    "runner": {
                        "dont_store_on_error": true,
                        "filter_results": ["[a-z]", "[A-Z]"]
                    }
                }
            },
            "plugin1": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 10
                }
            }
        },
        "args": {
            "daemon": {
                "workers": 2
            }
        }
    }
}

Plugin Pipelines

Trident supports defining plugin pipelines with individual steps executing a certain functionality. The steps are entirely defined in the JSON configuration file as described below.

Example: The following configuration has two plugins: plugin0 and plugin1. plugin0 is a normal plugin as described before and plugin1 is a plugin pipeline. The pipeline consists of three steps, the first step finds files using the library method entries, it has its own arguments defined by the args section and also the out section defining the name of the variable the output of the method might provide. The second step creates an archive from the identified files from the first step. The final step is a plugin is as described before which backs up the archive.

{
    "TRIDENT": {
        "logging_level": "INFO",
        "plugins": {
            "plugin0": {
                "path": "plugins.plugin",
                "plugin_args": {
                    "value": 0
                },
                "args": {
                    "runner": {
                        "dont_store_on_error": true,
                        "filter_results": ["[a-z]", "[A-Z]"]
                    }
                }
            },
            "plugin1": {
                "name": "Backup Files",
                "plugin_args": {},
                "args": {},
                "steps": [
                    {
                        "name": "Find Files",
                        "instruction": {
                            "ref": "plugins.lib.files.files.entries",
                            "type": "method",
                            "args": {
                                "path": "/path/to/folder",
                                "patterns": ["pattern"]
                            },
                            "out": {
                                "name": "path",
                                "all": true
                            }
                        }
                    },
                    {
                        "name": "Archive Files",
                        "instruction": {
                            "ref": "plugins.lib.files.files.archive_entry",
                            "type": "method",
                            "args": {
                                "archive": "/path/to/archive.tar.gz"
                            },
                            "out": {
                                "name": "archive",
                                "all": true
                            }
                        }
                    },
                    {
                        "name": "Backup Files",
                        "instruction": {
                            "name": "BackupArchive",
                            "ref": "plugins.backup_archive",
                            "type": "plugin",
                            "args": {},
                            "out": {
                                "name": "result",
                                "all": true
                            }
                        }
                    }
                ]
            }
        },
        "args": {
            "daemon": {
                "workers": 2
            }
        }
    }
}

The instruction section allows for the following arguments:

Developing Plugins

Trident plugins are normal Python modules and the actual plugin is a class that needs to be named just as the name of the Python module, so if you have the plugin find_file.py then the class in the plugin needs to be named FindFile.

The entry point of the plugin is always execute_plugin so when defining any new plugin this method needs to be present. See example below.

class FindFile:
    def execute_plugin(self, thread_event, file_name):
        # Find the file with the name file_name on the system
        ...

Plugin Library

The Trident plugin library offers functionality to do some common operations on the host system, for example, walking the file system to find files, opening ports, sending packets and more.

The Trident plugin library uses its own library to implement crucial functionality like TCP, UDP, ICMP and more to not rely on external modules. These are available to anyone wishing to extend the Trident plugin library with their own functionality.

The library tries to implement each functionality using primarily generators to allows for better asynchronous behavior. For each function that return a generator there is also a iterative version that returns the finished operation, the name of the iterative version is usually the original name with _iter appended to it.

Variables

By default Trident tries to pass the thread_event parameter to the plugin, this is of type Event from the threading library and is used to allow the system to halt the execution of Trident with keyboard interrupt. Note that the plugin needs to implement periodic checks of this variable using thread_event.is_set() in order for this to work. If the plugin does not implement the check then if Trident is passed an interrupt by the system then Trident will not be able to exit until the plugin operations finish.

Trident allows the user to pass any initial parameters to the plugin by defining the args key in the plugin configuration followed by the value. Trident will try to pass each variable found in the args section of the plugin configuration to the execute_plugin method, so the method needs to have these parameters defined as well.

Creating and Loading States

When running plugins that take some time to run it might be necessary to interrupt the execution sometimes and continue later. Therefore Trident supports saving and loading plugin states using checkpoints similar to data stores. The checkpoints are saved as JSON but the format is up to the author of the plugin as they need to decide when to save and load the state.

In order to save and load the states of the plugin the plugin needs to implement the getter and setter for plugin_state as described in the below minimal example.

class FindFile:
    def __init__(self):
        self._state = None

    @property
    def plugin_state(self):
        return self._state

    @plugin_state.setter
    def plugin_state(self, state):
        self._state = state

    def execute_plugin(self, thread_event, file_name):
        # Find the file with the name file_name on the system, the starting point is in self._state
        ...

The getter (property) and the setter (plugin_state.setter) can be implemented in any way the author wants as long as the getter returns a JSON serializable object.