Functions

Resilient Functions send data to external code — function processors — when triggered by Resilient rules and workflows. This external code can then perform integration work, for example:

  • Performing a lookup (e.g., for information about a user or machine in an asset database), returning the data values found.
  • Searching SIEM logs (e.g., for an IP address, a URL or a server name), returning a list of events.
  • Sending a file attachment to a sandbox for analysis, producing a report and a collection of observables.
  • Open a ticket in an ITSM system with a type, name and description, returning the ticket-ID.
  • Trigger any other sort of external action, then returning results for use in workflows, tasks and other decisions.

Functions provide the Resilient administrator with a flexible toolbox they can use to build workflows that coordinate multiple activities.

In the Resilient platform, a Rule is configured on an incident, an artifact, or other object. When the rule fires, it runs a Workflow that might have multiple steps. Those steps can include Functions that send input parameters to the function processor using a Message Destination, receive the results, and use the result to update the Resilient incident, to decide the direction of subsequent workflow steps, or in a variety of other ways.

Writing a Function in Python

The Resilient Circuits framework in the Python SDK makes it extremely simple to develop and deploy custom functions using Python.

A Function Processor component, in this framework, is a Python class that implements function methods. These functions are called by the framework when the Resilient platform invokes the function from a workflow.

Defining the Function

Define the interface definition for the Function — the name of the function, and its inputs — in the Resilient platform. Each input parameter has a field-definition that gives it a name and data-type: String, Number, Time/Date and so on.

The function can be dropped onto a workflow, where you can define the data it works with (values for the input parameters). Workflows can include any combination of components and decision paths that switch according to conditions.

In the workflow, the function’s properties panel has sections to write static input parameters, scripts for assignment from incident properties, and output handling that passes the function result to later components or updates the incident. In this example, the inputs are set from an artifact, and the output is written as a note.

When you have the outline of your function and workflow, export the organization’s customizations (Administrator Settings -> Organization -> Export). This export will be used for codegen.

Code Generation

To write the Python code that performs the function’s integration logic, start by using codegen to generate a Python package with a boilerplate implementation. This package includes everything needed to make your function installable. Besides your code, it can include the function definition, custom fields, data tables, workflows and rules. When a Resilient administrator installs this package into their Resilient organization, all these customizations can be imported directly into the platform.

resilient-circuits codegen --package pckg_name --function func_name

The result is a directory containing the essential files for an installable Python package that implements the function(s) specified. Within this package, the function code itself is a simple script, such as the one below, that you can edit to add your integration logic. This script is a component with a method, decorated with @function() that tells the Resilient Circuits framework how to call it.

"""Function implementation"""

import logging
from resilient_circuits import ResilientComponent, function, StatusMessage, FunctionResult, FunctionError


class FunctionComponent(ResilientComponent):
    """Component that implements Resilient function 'lookup_model_by_id"""


    @function("lookup_model_by_id")
    def _lookup_model_by_id_function(self, event, *args, **kwargs):
        """Function: Lookup more information about the specified ID"""
        try:
            # Get the function parameters:
            model_id = kwargs.get("model_id")  # text

            log = logging.getLogger(__name__)
            log.info("model_id: %s", model_id)

            # PUT YOUR FUNCTION IMPLEMENTATION CODE HERE
            #  yield StatusMessage("starting...")
            #  yield StatusMessage("done...")

            results = {
                "value": "xyz"
            }

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()

First, the function gets its parameters (model_id, in this case). The boilerplate implementation logs the values for ease of debugging, although you may want to change that if it’s too noisy.

At any stage during the function’s processing, you can yield StatusMessage("...") a status message that displays to the user in the Action Status dialog. If your function might run for several seconds or minutes, this can be a useful way to show progress.

The results are a Python dictionary, containing named values that will be available in the workflow output and post-processing script. Typically, functions should return a small ‘results’ dictionary with one or a few named values, and you should include enough documentation to allow your users to understand how to find and use these results in a custom workflow.

Running the Example

Before running the function, you must install this package so that resilient-circuits can load it. The most convenient way to install during development is with pip’s editable flag (or -e). Using this, you can edit your source files directly without needing to reinstall after any changes.

pip install --editable ./pckg_name/

Run the integration code from the command-line, with resilient-circuits run. The framework reads your configuration file, connects to the Resilient platform, finds and loads all the installed components, and then subscribes to the message destination for each function processor component. Leave it running; when a function is invoked, the code handles it.

$ resilient-circuits run
2018-04-12 16:10:31,814 INFO [app] Configuration file: /home/integration/.resilient/app.config
2018-04-12 16:10:31,816 INFO [app] Resilient server: myserver.resilientsystems.com
2018-04-12 16:10:31,817 INFO [app] Resilient user: api@example.com
2018-04-12 16:10:31,818 INFO [app] Resilient org: PartnerLab
2018-04-12 16:10:31,818 INFO [app] Logging Level: INFO
2018-04-12 16:10:38,075 INFO [component_loader] Loading 1 components
2018-04-12 16:10:38,076 INFO [component_loader] 'fn_model.components.lookup_model_by_id.FunctionComponent' loading
2018-04-12 16:10:38,089 INFO [stomp_component] Connect to myserver.resilientsystems.com:65001
2018-04-12 16:10:38,090 INFO [actions_component] 'fn_model.components.lookup_model_by_id.FunctionComponent' function 'lookup_model_by_id' registered to 'function_example'
2018-04-12 16:10:38,091 INFO [app] Components loaded
2018-04-12 16:10:38,094 INFO [app] App Started
2018-04-12 16:10:38,196 INFO [actions_component] STOMP attempting to connect
2018-04-12 16:10:38,198 INFO [stomp_component] Connect to Stomp...
2018-04-12 16:10:38,199 INFO [client] Connecting to myserver.resilientsystems.com:65001 ...
2018-04-12 16:10:38,825 INFO [client] Connection established
2018-04-12 16:10:39,090 INFO [client] Connected to stomp broker [session=ID:ip-1-2-3-252.srv.resilientsystems.com-35733-1523282148180-5:243, version=1.2]
2018-04-12 16:10:39,092 INFO [stomp_component] Connected to failover:(ssl://myserver.resilientsystems.com:65001)?maxReconnectAttempts=1,startupMaxReconnectAttempts=1
2018-04-12 16:10:39,093 INFO [stomp_component] Client HB: 0  Server HB: 15000
2018-04-12 16:10:39,094 INFO [stomp_component] No Client heartbeats will be sent
2018-04-12 16:10:39,095 INFO [stomp_component] Requested heartbeats from server.
2018-04-12 16:10:39,098 INFO [actions_component] STOMP connected.
2018-04-12 16:10:39,205 INFO [actions_component] Subscribe to message destination 'function_example'
2018-04-12 16:10:39,206 INFO [stomp_component] Subscribe to message destination actions.201.function_example

The framework is now running, waiting for the function to be called.

Calling the Function from your Workflow

Trigger the workflow using a rule: either a Menu-Item Rule that shows an action on its object (incident, artifact, task, etc.) when the conditions are met, or an Automatic Rule that runs the workflow when an object (incident, artifact, task, etc.) is created or modified and meets the conditions that you specify.

For this example, use a Menu Item Rule since the workflow is meant to run on an artifact object.

Once the rule is executed on the artifact object, an action can be found on the “Actions” menu available from the […] button beside the artifact; similarly for tasks, notes, and so on.

Select the action to start the workflow and call your function.

At the integration console, you can see the function message arrive, including the logging message to print the function’s parameters as part of the boilerplate code.

2018-04-12 16:24:23,235 INFO [actions_component] Event: <lookup_model_by_id[] (id=219, workflow=lookup_model, user=who@example.com) 2018-04-12 16:23:20.221000> Channel: functions.function_example
2018-04-12 16:24:23,237 INFO [lookup_model_by_id] model_id: 0e6499e91482f47df46fcaebb28bac985193aab331beeb5bc553162b422c1f21

The Action Status menu shows whether each function is pending (queued for delivery to the function processor), processed successfully, or with an error. Here you can see that the action completed with success, and included a status message.

Additional Resources

More Code Examples