Package: sekoia-automation-sdk
Sekoia.io Automation Python SDKSDK to create Sekoia.io playbook modules.Modules can define:Triggers: daemons that create events that will start a playbook runActions: short-lived programs that constitute the main playbook nodes. They take arguments and produce a result.Create a triggerHere is how you could define a very basic trigger:fromsekoia_automation.moduleimportModulefromsekoia_automation.triggerimportTriggerclassMyTrigger(Trigger):defrun(self):whileTrue:# Do some stuffself.send_event('event_name',{'somekey':'somevalue'})# Maybe wait some timeif__name__=="__main__":module=Module()module.register(MyTrigger)module.run()You can access the Trigger's configuration withself.configurationand the module configuration withself.module.configuration.Attach files to an eventYou can attach files to an event so that these files are available to the playbook runs.Here is how you could crete a file that should be available to the playbook run:importosfromsekoia_automationimportconstantsfromsekoia_automation.triggerimportTriggerclassMyTrigger(Trigger):defrun(self):whileTrue:# Create a directory and a filedirectory_name="test_dir"dirpath=os.path.join(constants.DATA_STORAGE,directory_name)os.makedirs(dirpath)withopen(os.path.join(dirpath,"test.txt")"w")asf:f.write("Hello !")# Attach the file to the eventself.send_event('event_name',{'file_path':'test.txt'},directory_name)# Maybe wait some timePlease note that:send_event's third argument should be the path of a directory, relative toconstants.DATA_STORAGEThe directory will be the root of the playbook run's storage ("test.txt" will exist, not "test_dir/test.txt")You can ask the SDK to automatically remove the directory after it was copied withremove_directory=TrueYou should always dofrom sekoia_automation import constantsand useconstants.DATA_STORAGEso that it is easy to mockWhen attaching a single file to a playbook run, you can use thewritefunction to create the file:fromsekoia_automation.storageimportwritefromsekoia_automation.triggerimportTriggerclassMyTrigger(Trigger):defrun(self):whileTrue:# Simple creation of a filefilepath=write('test.txt',{'event':'data'})# Attach the file to the eventself.send_event('event_name',{'file_path':os.path.basename(filepath)},os.path.dirname(directory_name))# Maybe wait some timePersisting data to diskMost of the time, triggers have to maintain some state do to their work properly (such as a cursor).
In order to make sure that this data survives a reboot of the Trigger (which can happen with no reason),
it is useful to persist it to the trigger's storage.When the manipulated data is JSON serializable, it is recommended to use thePersistentJSONclass to do
so (instead ofshelve). Used as a context manager, this class will make sure the python dict is properly
synchronised:fromsekoia_automation.triggerimportTriggerfromsekoia_automation.storageimportPersistentJSONclassMyTrigger(Trigger):defrun(self):whileTrue:# Read and update statewithPersistentJSON('cache.json')ascache:# Use cache as you would use a normal python dictCreate an actionHere is how you could define a very basic action that simply adds its arguments as result:fromsekoia_automation.moduleimportModulefromsekoia_automation.actionimportActionclassMyAction(Action):defrun(self,arguments):returnarguments# Return value should be a JSON serializable dictif__name__=="__main__":module=Module()module.register(MyAction)module.run()There are a few more things you can do within an Action:Access the Module's configuration withself.module.configurationAdd log messages withself.log('message', 'level')Activate an output branch withself.set_output('malicious')or explicitely disable another withself.set_output('benign', False)Raise an error withself.error('error message'). Note that raised exceptions that are not catched by your code will be automatically handled by the SDKWorking with filesActions can read and write files the same way a Trigger can:fromsekoia_automationimportconstantsfilepath=os.path.join(constants.DATA_STORAGE,"test.txt")It is a common pattern to accept JSON arguments values directly or inside a file. The SDK provides an helper to easily read such arguments:classMyAction(Action):defrun(self,arguments):test=self.json_argument("test",arguments)# Do somehting with testThe value will automatically be fetched fromtestif present, or read from the file attest_path.The SDK also provides an helper to do the opposite with results:classMyAction(Action):defrun(self,arguments):returnself.json_result("test",{"some":"value"})This will create a dict withtest_pathby default ortestif the last argument was passed directly.Same Docker Image for several itemsIn most cases, it makes sense to define several triggers and / or actions sharing the same code and the same docker image.In this case, here is how you should define the main:if__name__=="__main__":module=Module()module.register(Trigger1,"command_trigger1")module.register(Trigger2,"command_trigger2")module.register(Action1,"command_action1")module.register(Action2,"command_action2")module.run()The corresponding commands need to be correctly set in the manifests as "docker_parameters".Use with PydanticIt is recommended to use Pydantic to develop new modules. This should ease development.Module ConfigurationA pydantic model can be used asself.module.configurationby adding type hints:classMyConfigurationModel(BaseModel):field:strclassMyModule(Module):configuration:MyConfigurationclassMyAction(Action):module:MyModuleTriggersThe Trigger configuration can also be a pydantic model by adding a type hint:classMyTrigger(Trigger):configuration:MyConfigurationModelYou can also specify the model of created events by setting theresults_modelattribute:classEvent(BaseModel):field:str="value"classMyTrigger(Trigger):results_model=EventActionsYou can use a pydantic model as action arguments by adding a type hint:classActionArguments(BaseModel):field:str="value"classMyAction(Action):defrun(self,arguments:ActionArguments):...The model of results can also be specified by setting theresults_modelattribute:classResults(BaseModel):field:str="value"classMyAction(action):results_model=ResultsAutomatically generating manifestsWhen using pydantic models to describe configurations, arguments and results, manifests
can be automatically generated:$ poetry run sekoia-automation generate-filesThis will do the following:Generatemain.pyGenerate a manifest for each actionGenerate a manifest for each triggerUpdate the module's manifestFor better results, it is recommended to set thenameanddescriptionattributes in Actions
and Triggers.