Several interfaces exist for extending Diffy:
Analysis (diffy.plugins.bases.analysis)
Collection (diffy.plugins.bases.collection)
Payload (diffy.plugins.bases.payload)
Persistence (diffy.plugins.bases.persistence)
Target (diffy.plugins.bases.target)
Inventory (diffy.plugins.bases.inventory)
Each interface has its own functions that will need to be defined in order for your plugin to work correctly. See Plugin Interfaces for details.
Structure¶
A plugins layout generally looks like the following:
setup.py
diffy_pluginnae/
diffy_pluginname/__init__.py
diffy_pluginname/plugin.py
The __init__.py
file should contain no plugin logic, and at most, a VERSION
= ‘x.x.x’ line. For example, if you want to pull the version using
pkg_resources (which is what we recommend), your file might contain:
try:
VERSION = __import__('pkg_resources') \
.get_distribution(__name__).version
except Exception as e:
VERSION = 'unknown'
Inside of plugin.py
, you’ll declare your Plugin class, inheriting from the
parent classes that establish your plugin’s functionality:
import diffy_pluginname
from diffy.plugins.bases import AnalysisPlugin, PersistencePlugin
class PluginName(AnalysisPlugin):
title = 'Plugin Name'
slug = 'pluginname'
description = 'My awesome plugin!'
version = diffy_pluginname.VERSION
author = 'Your Name'
author_url = 'https://github.com/yourname/diffy_pluginname'
def widget(self, request, group, **kwargs):
return "<p>Absolutely useless widget</p>"
And you’ll register it via entry_points
in your setup.py
:
setup(
# ...
entry_points={
'diffy.plugins': [
'pluginname = diffy_pluginname.analysis:PluginName'
],
},
)
You can potentially package multiple plugin types in one package, say you want to create a source and destination plugins for the same third-party. To accomplish this simply alias the plugin in entry points to point at multiple plugins within your package:
setup(
# ...
entry_points={
'diffy.plugins': [
'pluginnamesource = diffy_pluginname.plugin:PluginNameSource',
'pluginnamedestination = diffy_pluginname.plugin:PluginNameDestination'
],
},
)
Once your plugin files are in place and the setup.py
file has been
modified, you can load your plugin by reinstalling diffy:
(diffy)$ pip install -e .
That’s it! Users will be able to install your plugin via pip install <package
name>
.
See also
For more information about python packages see Python Packaging
Plugin Interfaces¶
In order to use the interfaces all plugins are required to inherit and override unimplemented functions of the parent object.
Analysis¶
Analysis plugins are used when you are trying to scope or evaluate information across a cluster. They can either process information locally or used an external system (i.e. for ML).
The AnalysisPlugin exposes on function:
def run(self, items, **kwargs):
# run analysis on items
Diffy will pass all items collected it will additionally pass the optional baseline flag if the current configuration is deemed to be a baseline.
Collection¶
Collection plugins allow you to collect information from multiple hosts. This provides flexibility on how information is collected, depending on the infrastructure available to you.
The CollectionPlugin requires only one function to be implemented:
def get(self, targets, incident, command, **kwargs) --> dict:
"""Queries system target.
:returns command results as dict {
'command_id': [
{
'instance_id': 'i-123343243',
'status': 'success',
'collected_at' : 'datetime'
'stdout': {json osquery result}
}
...
{additional instances}
]
}
"""
The incident string is intended to document a permanent identifier for your investigation. You may insert any unique ticketing system identifier (for example, DFIR-21996), or comment, here.
Payload¶
Diffy includes the ability to modify the payload for any given command. In general this payload is the dynamic generation of commands sent to the target. For instance if you are simply running a netstat payload you may have to actually run a series of commands to generate a JSON output from the netstat command.
Here again the incident is passed to be dynamically included into the commands if applicable.
The PayloadPlugin requires only one function to be implemented:
def generate(self, incident, **kwargs) --> dict:
# list of commands to be sent to the target
Persistence¶
Persistence plugins give Diffy to store the outputs of both collection and analysis to location other than memory. This is useful for baseline tasks or persisting data for external analysis tasks.
The PersistencePlugin requires two functions to be implemented:
def get(self, key, **kwargs):
# retrieve from location
def save(self, key, item, **kwargs):
# save to location
Target¶
Target plugins give Diffy the ability to interact with external systems to resolve targets for commands.
The TargetPlugin class requires one function to be implemented:
def get(self, key, **kwargs):
# fetch targets based on key
Inventory¶
Inventory plugins interact with asset inventory services to pull a list of targets for baselining and analysis.
Inheriting from the InventoryPlugin class requires that you implement a process
method:
def process(self, **kwargs):
# Process a new set of targets from a desired source.
#
# This method should handle the interaction with your desired source,
# and then send the results to :meth:`diffy_api.core.async_baseline`.
#
# If you poll the source regularly, ensure that you
# only request recently deployed assets.
Testing¶
Diffy provides a basic py.test-based testing framework for extensions.
In a simple project, you’ll need to do a few things to get it working:
setup.py¶
Augment your setup.py to ensure at least the following:
setup(
# ...
install_requires=[
'diffy',
]
)
conftest.py¶
The conftest.py
file is our main entry-point for py.test. We need to
configure it to load the Diffy pytest configuration:
from diffy.tests.conftest import * # noqa
Running Tests¶
Running tests follows the py.test standard. As long as your test files and
methods are named appropriately (test_filename.py
and test_function()
)
you can simply call out to py.test:
$ py.test -v
============================== test session starts ==============================
platform darwin -- Python 2.7.10, pytest-2.8.5, py-1.4.30, pluggy-0.3.1
cachedir: .cache
collected 346 items
diffy/plugins/diffy_acme/tests/test_aws.py::test_ssm PASSED
=========================== 1 passed in 0.35 seconds ============================
See also
Diffy bundles several plugins that use the same interfaces mentioned above.