Exemplo n.º 1
0
    def test_dictionary_merge(self, input_dict):
        """ Test that the dictionary merge function works as expected.
        The function should return a dictionary
        If 1 or all inputs or None or empty it should not affect the return type"""
        helper = ICDXHelper({})
        result = helper.merge_dicts(input_dict)

        assert (isinstance(result, dict))
Exemplo n.º 2
0
    def test_file_artifact_parse_success(self, search_result):
        """ Test that the file artifact function works as expected
        The function should return a dictionary
        With a known good input the return should be Truth-y (has elements)
        """
        helper = ICDXHelper({})
        result = helper.parse_for_file_artifacts(search_result)

        assert (isinstance(result, dict))
        assert (bool(result))
Exemplo n.º 3
0
    def test_dictionary_merge_failure(self, input_dict):
        """ Test that the dictionary merge function handles empty inputs.
        The function should return a dictionary
        If 1 or all inputs or None or empty it should not affect the return type
        But if all inputs are None or empty the return should be False-y i.e empty dict"""
        helper = ICDXHelper({})
        result = helper.merge_dicts(input_dict)

        assert (isinstance(result, dict))
        assert (bool(result) == False)
Exemplo n.º 4
0
 def test_multi_artifact_parse_success(self, search_result):
     """ Test that the file artifact function works as expected
     The function should return a dictionary
     With a known good input the return should be Truth-y (has elements)
     """
     helper = ICDXHelper({})
     result = helper.search_for_artifact_data(search_result)
     assert (isinstance(result, dict))
     assert (result is not None)
     assert '10.1.1.1' in result['IP Address']
     if 'email object' in search_result['name']:
         assert '127.0.0.1' in result['IP Address']
 def __init__(self, opts):
     super(ICDXComponentObserver, self).__init__(opts)
     self.options = opts.get("fn_icdx", {})
     add_methods_to_global()
     helper = ICDXHelper(self.options)
     if "pytest" in sys.modules:
         """
         Reaching here indicates that the component is invoked from within a testing session.
         In this case, don't start the forwarder
         """
         log.info(
             "Running within a test environment. Not starting consumer")
     else:
         if helper.str_to_bool(
                 self.options.get("icdx_forwarder_toggle", None)):
             self.setup_observer()
    def test_successful_instantiation(self):
        """Should not raise an exception when AMQPAsyncConsumer is instantiated with correct params"""

        obj = AMQPAsyncConsumer(host="192.168.1.254",
                                port=1,
                                virtual_host="dx",
                                username="******",
                                amqp_password="******",
                                rest_client=SimpleClient(),
                                helper=ICDXHelper(options={}))
 def test_full_unsuccessful_instantiation(self):
     """Should raise an exception when AMQPAsyncConsumer is instantiated with malformed params"""
     with pytest.raises(Exception) as e_info:
         obj = AMQPAsyncConsumer(host="192.168.1.254",
                                 port=1,
                                 virtual_host="dx",
                                 username="******",
                                 amqp_password="******",
                                 rest_client=SimpleClient(),
                                 helper=ICDXHelper())
    def test_restclient_validation(self):
        """Should raise an exception when AMQPAsyncConsumer is instantiated without params"""

        obj = AMQPAsyncConsumer(host="192.168.1.254",
                                port=1,
                                virtual_host="dx",
                                username="******",
                                amqp_password="******",
                                rest_client=SimpleClient(),
                                helper=ICDXHelper(options={}))

        assert isinstance(getattr(obj, "_client"), SimpleClient)

        obj_with_no_client = AMQPAsyncConsumer(host="192.168.1.254",
                                               port=1,
                                               virtual_host="dx",
                                               username="******",
                                               amqp_password="******",
                                               rest_client=123,
                                               helper=ICDXHelper(options={}))
        assert isinstance(getattr(obj_with_no_client, "_client"), type(None))
    def setup_observer(self):
        """
        Attempts to create an instance of the Consumer class and begin consuming forwarded messages.
        If the connection is closed unexpectedly, Consumer attempts to reopen it.

        AMQP sends hearbeats to keep a connection alive, if 2 consecutive heartbeats are missed
        the Consumer will completly stop.
        """

        helper = ICDXHelper(self.options)
        logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)

        # Init the Consumer, passing in an instance of RestClient and ICDXHelper as injected deps
        observer = AMQPAsyncConsumer(
            host=helper.get_config_option("icdx_amqp_host"),
            port=helper.get_config_option("icdx_amqp_port", True),
            virtual_host=helper.get_config_option("icdx_amqp_vhost"),
            username=helper.get_config_option("icdx_amqp_username"),
            amqp_password=helper.get_config_option("icdx_amqp_password"),
            rest_client=self.rest_client(),
            helper=ICDXHelper(self.options))
        """
        Start our consumer, will attempt a connection to ICDX
        When a connection is attempted a chain of functions are called
        Each of these functions has a callback which in turn will trigger the next part of the setup       
        Interfacing with Pika asynchronously is done by passing in callback methods you would like to have invoked
        when a certain event completes.
        
        The on_message function is triggered to potentially create an incident every time a new forwarded event
        comes off the message queue
        
        The below pattern is similar to how threads work wherein all logic is encapsulated -- we only call run()
        All functions are event-based which requires that the output of one function be the input to another.
        """
        try:
            # Create a thread which targets the observers run function
            # N.B note the lack of parentheses on observer.run
            res_daemon_thread = threading.Thread(target=observer.run)
            # Make the thread a daemon (background)
            res_daemon_thread.daemon = True
            # Start daemon thread in bg so rest of resilient-circuits is not blocked
            res_daemon_thread.start()

        except Exception as e:
            log.error(
                "Encountered an issue when starting the thread: {}".format(
                    str(e)))
Exemplo n.º 10
0
    def _icdx_get_archive_list_function(self, event, *args, **kwargs):
        """Function: The Get Archive List API is used to return a list of archives in the ICDx system. The response is an unsorted list of archive metadata objects which can then be searched by a user."""

        log = logging.getLogger(__name__)
        try:
            rc = ResultPayload(ICDX_SECTION, **kwargs)

            helper = ICDXHelper(self.options)
            yield StatusMessage(
                "Attempting to gather config and setup the AMQP Client")
            try:
                # Initialise the AmqpFacade, pass in config values
                amqp_client = AmqpFacade(
                    host=helper.get_config_option("icdx_amqp_host"),
                    port=helper.get_config_option("icdx_amqp_port", True),
                    virtual_host=helper.get_config_option("icdx_amqp_vhost"),
                    username=helper.get_config_option("icdx_amqp_username"),
                    amqp_password=helper.get_config_option(
                        "icdx_amqp_password"))

                yield StatusMessage(
                    "Config options gathered and AMQP client setup.")
            except Exception:
                raise FunctionError(
                    "Encountered error while initialising the AMQP Client")
            # Prepare request payload
            request = {"id": GET_ARCHIVE_LIST_CODE}

            # Make the call to ICDx and get a handle on any results
            archives, status = amqp_client.call(json.dumps(request))

            yield StatusMessage(
                "ICDX call complete with status: {}".format(status))

            # If status code in the message header is 200 we have results, 204 is empty response.
            results = rc.done(
                success=False if status != 200 else True,
                content={
                    "archives":
                    (json.loads(archives) if archives is not None else None)
                })
            results.update({
                "archives":
                (json.loads(archives) if archives is not None else None)
            })

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
            log.info("Complete")
        except Exception:
            yield FunctionError()
    def test_default_port_usage(self):
        """Should raise an exception when AMQPAsyncConsumer is instantiated without params"""

        obj = AMQPAsyncConsumer(host="192.168.1.254",
                                port=1,
                                virtual_host="dx",
                                username="******",
                                amqp_password="******",
                                rest_client=SimpleClient(),
                                helper=ICDXHelper(options={}))

        assert isinstance(getattr(obj, "_port"), int)
        assert getattr(obj, "_port") == 1

        obj_with_no_client = AMQPAsyncConsumer(host="192.168.1.254",
                                               port="notaPort",
                                               virtual_host="dx",
                                               username="******",
                                               amqp_password="******",
                                               rest_client=SimpleClient(),
                                               helper=123)
        assert isinstance(getattr(obj_with_no_client, "_port"), int)
        assert getattr(obj_with_no_client, "_port") == 5672
    def _icdx_get_event_function(self, event, *args, **kwargs):
        """Function: Takes in an input of a UUID for an event and attempts to get the details of this event from the ICDx platform."""

        try:
            rc = ResultPayload(ICDX_SECTION, **kwargs)

            # Get the function parameters:
            icdx_uuid = kwargs.get("icdx_uuid")  # text

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

            if icdx_uuid is None:
                raise FunctionError(
                    "Encountered error: icdx_uuid may not be None")

            helper = ICDXHelper(self.options)
            yield StatusMessage(
                "Attempting to gather config and setup the AMQP Client")
            try:
                # Initialise the AmqpFacade, pass in config values
                amqp_client = AmqpFacade(
                    host=helper.get_config_option("icdx_amqp_host"),
                    port=helper.get_config_option("icdx_amqp_port", True),
                    virtual_host=helper.get_config_option("icdx_amqp_vhost"),
                    username=helper.get_config_option("icdx_amqp_username"),
                    amqp_password=helper.get_config_option(
                        "icdx_amqp_password"))
                yield StatusMessage(
                    "Config options gathered and AMQP client setup.")
            except Exception:
                raise FunctionError(
                    "Encountered error while initialising the AMQP Client")

            yield StatusMessage(
                "Sending request to ICDx with a UUID of {0}".format(icdx_uuid))
            # Prepare request payload
            request = {
                "id": GET_EVENT_API_CODE,
                "uuid": icdx_uuid,
            }

            # Make the call to ICDx and get a handle on any results
            search_result_payload, status = amqp_client.call(
                json.dumps(request))

            yield StatusMessage(
                "ICDx call complete with status: {}".format(status))

            # Define variables related to artifact search to avoid reference before assignment
            artifact_dictionary = None
            artifact_keys_as_list = None
            artifact_values_as_list = None

            if 'connection'.encode('utf-8') in search_result_payload:
                yield StatusMessage(
                    "Network Connection attribute found on Event. Parsing for artifact data"
                )

            if 'file'.encode('utf-8') in search_result_payload:
                yield StatusMessage(
                    "File attribute found on Event. Parsing for artifact data")

            if 'email'.encode('utf-8') in search_result_payload:
                yield StatusMessage(
                    "Email attribute found on Event. Parsing for artifact data"
                )

            if status == 200:
                # Parse the search result for any artifacts
                artifact_dictionary = helper.search_for_artifact_data(
                    json.loads(search_result_payload))
                """Lastly get the keys and the values as separate lists
                This is needed in v30 or lower.
                Post-processing script shows how to use the data in both v30 and v31
                """
                artifact_keys_as_list = artifact_dictionary.keys()
                artifact_values_as_list = artifact_dictionary.values()
            elif status == 400:
                yield StatusMessage(
                    "Received a 400 (Bad Request). Check the formatting of your ICDx Query"
                )
            elif status == 401:
                yield StatusMessage(
                    "Received a 401 (Auth). Check the credentials in app.config"
                )

            # Return the results
            event = None if status != 200 else json.loads(
                search_result_payload.decode('utf-8')),
            artifacts = artifact_dictionary,
            artifact_keys_as_list = None if isinstance(
                artifact_keys_as_list,
                type(None)) else list(artifact_keys_as_list)
            artifact_values_as_list = None if isinstance(
                artifact_values_as_list,
                type(None)) else list(artifact_values_as_list)
            results = rc.done(success=False if status != 200 else True,
                              content={
                                  "event":
                                  event,
                                  "artifacts":
                                  artifact_dictionary,
                                  "artifact_keys_as_list":
                                  artifact_keys_as_list,
                                  "artifact_values_as_list":
                                  artifact_values_as_list
                              })
            results.update({
                "event": event,
                "artifacts": artifact_dictionary,
                "artifact_keys_as_list": artifact_keys_as_list,
                "artifact_values_as_list": artifact_values_as_list
            })

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
Exemplo n.º 13
0
    def test_file_artifact_parse_failure(self, search_result):
        helper = ICDXHelper({})
        result = helper.parse_for_file_artifacts(search_result)

        assert (not result)
        assert (bool(result) is False)
    def _icdx_find_events_function(self, event, *args, **kwargs):
        """Function: Takes a number of parameters in a search request and attempts to gather events from the ICDx Platform. Returns a response containing a list of events or a response with a 204 status code when no results are found."""
        try:
            # Get the function parameters:
            icdx_search_request = self.get_textarea_param(
                kwargs.get("icdx_search_request"))  # textarea

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

            if icdx_search_request is None:
                raise FunctionError(
                    "Encountered error: icdx_search_request may not be None")

            helper = ICDXHelper(self.options)
            yield StatusMessage(
                "Attempting to gather config and setup the AMQP Client")
            try:

                # Initialise the AmqpFacade, pass in config values
                amqp_client = AmqpFacade(
                    host=helper.get_config_option("icdx_amqp_host"),
                    port=helper.get_config_option("icdx_amqp_port", True),
                    virtual_host=helper.get_config_option("icdx_amqp_vhost"),
                    username=helper.get_config_option("icdx_amqp_username"),
                    amqp_password=helper.get_config_option(
                        "icdx_amqp_password"))

                yield StatusMessage(
                    "Config options gathered and AMQP client setup.")
            except Exception as e:
                yield StatusMessage(
                    "Encountered error while initialising the AMQP Client; Reason (if any): {0}"
                    .format(str(e)))
                raise FunctionError()
            # Prepare request payload
            try:
                search_payload = json.loads(icdx_search_request)

                # Set the hard limit for displaying in the UI
                """
                if hard limit is not set, it defaults to 100
                if hard limit is set and exceeds 100 that is the new limit
                if hard limit is set and does not exceed 100, 100 is the limit
                Max limit appears to be 500
                """
                search_payload['hard_limit'] = max([
                    helper.safe_cast(val=i, to_type=int) for i in
                    [helper.get_config_option("icdx_search_limit", True), 100]
                    if i is not None
                ])

                # Payload is now a dict -- read 'limit' and replace with app.config value if exceeds it
                if search_payload['limit'] > search_payload['hard_limit']:
                    search_payload['limit'] = search_payload['hard_limit']

                # All okay, request has valid JSON and an ID
                yield StatusMessage(
                    "Request payload validated. Sending request with a where attribute of {0} and a filter attribute of {1}"
                    .format(
                        search_payload.get('where', None)
                        or '"No Where Condition"',
                        search_payload.get('filter', None)
                        or '"No Filter Condition"'))

            except Exception:
                raise FunctionError(
                    "Problem parsing the JSON for search request")

            # Make the call to ICDx and get a handle on any results
            search_result, status = amqp_client.call(
                json.dumps(search_payload))

            yield StatusMessage(
                "ICDX call complete with status: {}".format(status))
            result_set, num_of_results = None, 0

            if not search_result:
                log.info("Received an empty result set for search query.")

            else:
                log.info("Received response. Parsing for information")
                # Get results dict and number of results
                if status == 200:
                    res = json.loads(search_result)

                    result_set, num_of_results = res["result"], len(
                        res["result"])
                elif status == 400:
                    yield StatusMessage(
                        "Received a 400 (Bad Request). Check the formatting of your ICDx Query"
                    )

            results = {
                "inputs": {
                    "icdx_search_request": search_payload
                },
                "success": (False if status != 200 else True),
                "result_set": result_set,
                "num_of_results": num_of_results,
                "execution_time": int(time.time() * 1000)
            }
            yield StatusMessage(
                "Finishing. Received results: {}. Number of results: {}".
                format(results["success"], num_of_results))
            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()