diff --git a/.gitignore b/.gitignore index a1b41dd..ad1c80c 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,7 @@ docs/_build/ # PyBuilder target/ -.idea/ \ No newline at end of file +.idea/ + +# IDE +.vscode/ diff --git a/pyFireEye/hx/__init__.py b/pyFireEye/hx/__init__.py index 5029c82..850a3d4 100644 --- a/pyFireEye/hx/__init__.py +++ b/pyFireEye/hx/__init__.py @@ -12,7 +12,9 @@ Indicators, \ IndicatorCategories, \ Quarantine, \ - Scripts + Scripts, \ + ProcessTracker, \ + MessageBus class HX: @@ -48,6 +50,8 @@ def __init__(self, hx_host, hx_port=None, verify=False, token_auth=False, userna self.scripts = Scripts(hx_host=hx_host, hx_port=hx_port, verify=verify, authenticator=self._authenticator) self.containment = Containment(hx_host=hx_host, hx_port=hx_port, verify=verify, authenticator=self._authenticator) self.custom_channels = CustomChannels(hx_host=hx_host, hx_port=hx_port, verify=verify, authenticator=self._authenticator) + self.process_tracker = ProcessTracker(hx_host=hx_host, hx_port=hx_port, verify=verify, authenticator=self._authenticator) + self.message_bus = MessageBus(hx_host=hx_host, hx_port=hx_port, verify=verify, authenticator=self._authenticator) def reauth(self): if self._authenticator.token_auth: @@ -62,4 +66,3 @@ def logout(self): self._authenticator.token_auth = True self._authenticator.logout() self._authenticator.token_auth = token_auth - diff --git a/pyFireEye/hx/hx_core.py b/pyFireEye/hx/hx_core.py index a67c175..2371154 100644 --- a/pyFireEye/hx/hx_core.py +++ b/pyFireEye/hx/hx_core.py @@ -11,7 +11,7 @@ class _HX: - + """ Base class for hx endpoints to derive from. will not work by itself unless you construct authenticator and set it manually, but you should never need to use it directly @@ -172,9 +172,9 @@ def __init__(self, hx_host, hx_port=None, verify=False, authenticator=None, user @expected_response(expected_status_code=200, expected_format=JSON) @template_request(method="GET", route="/hosts", - request_params=[HAS_ACTIVE_THREATS, HAS_ALERTS, HAS_EXECUTION_ALERTS, HAS_EXPLOIT_ALERTS, - HAS_EXPLOIT_BLOCKS, HAS_MALWARE_ALERTS, HAS_MALWARE_CLEANED, - HAS_MALWARE_QUARANTINED, HAS_PRESENCE_ALERTS, HAS_SHARE_MODE, HOSTS_SET_ID, LIMIT, + request_params=[HAS_ACTIVE_THREATS, HAS_ALERTS, HAS_EXECUTION_ALERTS, HAS_EXPLOIT_ALERTS, + HAS_EXPLOIT_BLOCKS, HAS_MALWARE_ALERTS, HAS_MALWARE_CLEANED, + HAS_MALWARE_QUARANTINED, HAS_PRESENCE_ALERTS, HAS_SHARE_MODE, HOSTS_SET_ID, LIMIT, OFFSET, SEARCH, SORT, FILTER_FIELD]) def get_list_of_hosts(self, has_active_threats=None, has_alerts=None, has_execution_alerts=None, has_exploit_alerts=None, has_exploit_blocks=None, has_malware_alerts=None, @@ -215,7 +215,7 @@ def new_file_acquisition_for_host(self, agent_id, req_path, req_filename, req_co @expected_response(expected_status_code=200, expected_format=JSON) @template_request(method="GET", route="/hosts//live", request_params=[OFFSET, LIMIT, SORT, FILTER_FIELD]) - def get_list_data_acquisitions_for_host(self, agent_id, offset=None, limit=None, sort=None, + def get_list_data_acquisitions_for_host(self, agent_id, offset=None, limit=None, sort=None, filter_field=None, **kwargs): return self._base_request(**kwargs) @@ -364,16 +364,16 @@ def delete_search_by_id(self, id, **kwargs): @template_request(method="POST", route="/searches//actions/stop") def stop_running_search(self, id, **kwargs): return self._base_request(**kwargs) - + @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/searches//hosts", + @template_request(method="GET", route="/searches//hosts", request_params=[OFFSET, LIMIT, SORT, FILTER_FIELD]) def get_list_hosts_states_for_search(self, id, offset=None, limit=None, sort=None, filter_field=None, **kwargs): return self._base_request(**kwargs) @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/searches//skipped_hosts", - request_params=[HAS_ACTIVE_THREATS, HAS_EXPLOIT_ALERTS, HAS_EXPLOIT_BLOCKS, HAS_MALWARE_ALERTS, + @template_request(method="GET", route="/searches//skipped_hosts", + request_params=[HAS_ACTIVE_THREATS, HAS_EXPLOIT_ALERTS, HAS_EXPLOIT_BLOCKS, HAS_MALWARE_ALERTS, HOSTS_SET_ID, LIMIT, OFFSET, SORT, FILTER_FIELD]) def get_list_hosts_skipped_for_search(self, id, has_active_threats=None, has_exploit_alerts=None, has_exploit_blocks=None, has_malware_alerts=None, host_set_id=None, @@ -392,7 +392,7 @@ def get_search_results(self, id, offset=None, limit=None, **kwargs): @expected_response(expected_status_code=200, expected_format=JSON) @template_request(method="GET", route="/searchs//results//hosts", - request_params=[HAS_ACTIVE_THREATS, HAS_EXPLOIT_ALERTS, HAS_EXPLOIT_BLOCKS, HAS_MALWARE_ALERTS, + request_params=[HAS_ACTIVE_THREATS, HAS_EXPLOIT_ALERTS, HAS_EXPLOIT_BLOCKS, HAS_MALWARE_ALERTS, HAS_SHARE_MODE, HOSTS_SET_ID, LIMIT, OFFSET, SORT, FILTER_FIELD]) def get_host_results_for_grid_row_search(self, id, row_id, has_active_threats=None, has_exploit_alerts=None, has_exploit_blocks=None, has_malware_alerts=None, has_share_mode=None, @@ -473,7 +473,7 @@ def bulk_replace_indicator_conditions(self, category, indicator, conditions, **k :param category: newline separated string of conditions :param indicator: - :param conditions: + :param conditions: :param kwargs: :return: """ @@ -486,7 +486,7 @@ def bulk_append_indicator_conditions(self, category, indicator, conditions, **kw :param category: newline separated string of conditions :param indicator: - :param conditions: + :param conditions: :param kwargs: :return: """ @@ -545,14 +545,14 @@ def get_condition_by_id(self, condition_id, **kwargs): return self._base_request(**kwargs) @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/conditions", + @template_request(method="GET", route="/conditions", request_params=[SEARCH, OFFSET, LIMIT, ENABLED, HAS_ALERTS, HAS_SHARE_MODE]) def get_list_conditions_all_hosts(self, search=None, offset=None, limit=None, enabled=None, has_alerts=None, has_share_mode=None, **kwargs): return self._base_request(**kwargs) @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/conditions//indicators", + @template_request(method="GET", route="/conditions//indicators", request_params=[OFFSET, LIMIT, CATEGORY_SHARE_MODE, SORT]) def get_indicators_using_condition(self, condition_id, offset=None, limit=None, category_share_mode=None, sort=None, **kwargs): @@ -560,7 +560,7 @@ def get_indicators_using_condition(self, condition_id, offset=None, limit=None, class IndicatorCategories(_HX): - + def __init__(self, hx_host, hx_port=None, verify=False, authenticator=None, username="", password=""): _HX.__init__(self, hx_host, hx_port=hx_port, verify=verify) if isinstance(authenticator, Authentication): @@ -571,7 +571,7 @@ def __init__(self, hx_host, hx_port=None, verify=False, authenticator=None, user @expected_response(expected_status_code=200, expected_format=JSON) @template_request(method="GET", route="/indicator_categories", request_params=[OFFSET, LIMIT, SHARE_MODE, SORT, FILTER_FIELD]) - def get_list_indicator_categories(self, offset=None, limit=None, share_mode=None, + def get_list_indicator_categories(self, offset=None, limit=None, share_mode=None, sort=None, filter_field=None, **kwargs): return self._base_request(**kwargs) @@ -582,7 +582,7 @@ def get_indicator_category(self, category, share_mode, **kwargs): @expected_response(expected_status_code=201, expected_format=JSON) @template_request(method="PUT", route="/indicator_categories/", - json_body=["display_name", "retention_policy", "ui_edit_policy", + json_body=["display_name", "retention_policy", "ui_edit_policy", "ui_signature_enabled", "ui_source_alerts_enabled"]) def new_indicator_category(self, category_name, display_name=None, retention_policy=None, ui_edit_policy=None, ui_signature_enabled=None, ui_source_alerts_enabled=None, **kwargs): @@ -615,18 +615,18 @@ def __init__(self, hx_host, hx_port=None, verify=False, authenticator=None, user self.AUTHENTICATION = authenticator elif username and password: self.AUTHENTICATION = Authentication(hx_host=hx_host, hx_port=hx_port, verify=verify, username=username, password=password) - + @expected_response(expected_status_code=200, expected_format=JSON) @template_request(method="GET", route="/alerts/") def get_alert_by_id(self, alert_id, **kwargs): return self._base_request(**kwargs) - + @expected_response(expected_status_code=200, expected_format=JSON) @template_request(method="GET", route="/alerts", request_params=[OFFSET, LIMIT, HAS_SHARE_MODE, SORT, FILTER_FIELD, FILTER_QUERY]) def get_list_alerts_all_hosts(self, offset=None, limit=None, has_share_mode=None, sort=None, filter_field=None, filterQuery=None, **kwargs): return self._base_request(**kwargs) - + @expected_response(expected_status_code=204, expected_format=DEFAULT) @template_request(method="DELETE", route="/alerts/") def suppress_alert(self, alert_id, **kwargs): @@ -648,14 +648,14 @@ def get_source_alert_by_id(self, source_alert_id, **kwargs): return self._base_request(**kwargs) @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/source_alerts/", + @template_request(method="GET", route="/source_alerts/", request_params=[PRIMARY_INDICATOR_ID, OFFSET, LIMIT, SORT]) def get_list_source_alerts_all_hosts(self, primary_indicator_id=None, offset=None, limit=None, sort=None, **kwargs): return self._base_request(**kwargs) @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/source_alerts//alerted_hosts", - request_params=[HAS_ACTIVE_THREATS, HAS_EXPLOIT_ALERTS, HAS_EXPLOIT_BLOCKS, + @template_request(method="GET", route="/source_alerts//alerted_hosts", + request_params=[HAS_ACTIVE_THREATS, HAS_EXPLOIT_ALERTS, HAS_EXPLOIT_BLOCKS, HAS_MALWARE_ALERTS, LIMIT, OFFSET, SEARCH, SORT, FILTER_FIELD]) def get_list_alerted_hosts_for_source_alert(self, source_alert_id, has_active_threates=None, has_exploit_alerts=None, has_exploit_blocks=None, @@ -664,7 +664,7 @@ def get_list_alerted_hosts_for_source_alert(self, source_alert_id, has_active_th return self._base_request(**kwargs) @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/source_alerts//alerts", + @template_request(method="GET", route="/source_alerts//alerts", request_params=[OFFSET, LIMIT, SORT, FILTER_FIELD]) def get_lists_of_alerts_for_source_alert(self, source_alert_id, offset=None, limit=None, sort=None, filter_field=None, **kwargs): return self._base_request(**kwargs) @@ -688,7 +688,7 @@ def __init__(self, hx_host, hx_port=None, verify=False, authenticator=None, user self.AUTHENTICATION = authenticator elif username and password: self.AUTHENTICATION = Authentication(hx_host=hx_host, hx_port=hx_port, verify=verify, username=username, password=password) - + @expected_response(expected_status_code=200, expected_format=JSON) @template_request(method="GET", route="/acqs/files", request_params=[SEARCH, OFFSET, LIMIT, SORT, FILTER_FIELD]) def get_list_file_acquisitions_all_hosts(self, search=None, offset=None, limit=None, @@ -773,7 +773,7 @@ def delete_host_bulk_acquisition_package_by_host(self, bulk_id, agent_id, **kwar return self._base_request(**kwargs) @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/acqs/bulk//hosts", + @template_request(method="GET", route="/acqs/bulk//hosts", request_params=[OFFSET, LIMIT, SORT, FILTER_FIELD]) def get_list_hosts_in_bulk_acquisition(self, bulk_id, offset=None, limit=None, sort=None, filter_field=None, **kwargs): @@ -781,7 +781,7 @@ def get_list_hosts_in_bulk_acquisition(self, bulk_id, offset=None, limit=None, s @expected_response(expected_status_code=200, expected_format=JSON) @template_request(method="GET", route="/acqs/bulk//skipped_hosts", - request_params=[HAS_ACTIVE_THREATS, HAS_EXPLOIT_ALERTS, HAS_EXPLOIT_BLOCKS, HAS_MALWARE_ALERTS, + request_params=[HAS_ACTIVE_THREATS, HAS_EXPLOIT_ALERTS, HAS_EXPLOIT_BLOCKS, HAS_MALWARE_ALERTS, HOSTS_SET_ID, LIMIT, OFFSET, SORT, FILTER_FIELD]) def get_hosts_skipped_in_bulk_acquisition(self, bulk_id, has_active_threats=None, has_exploit_alerts=None, has_exploit_blocks=None, has_malware_alerts=None, host_set_id=None, @@ -837,12 +837,12 @@ def __init__(self, hx_host, hx_port=None, verify=False, authenticator=None, user @expected_response(expected_status_code=200, expected_format=JSON) @template_request(method="GET", route="/hosts//quarantines", request_params=[OFFSET, LIMIT, SORT, FILTER_FIELD]) - def get_list_quarantined_files_for_host(self, agent_id=None, offset=None, limit=None, + def get_list_quarantined_files_for_host(self, agent_id=None, offset=None, limit=None, sort=None, filter_field=None, **kwargs): return self._base_request(**kwargs) @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/quarantines/", + @template_request(method="GET", route="/quarantines/", request_params=[OFFSET, LIMIT, SORT, FILTER_FIELD]) def get_list_quarantined_files(self, offset=None, limit=None, sort=None, filter_field=None, **kwargs): return self._base_request(**kwargs) @@ -949,7 +949,7 @@ def __init__(self, hx_host, hx_port=None, verify=False, authenticator=None, user self.AUTHENTICATION = Authentication(hx_host=hx_host, hx_port=hx_port, verify=verify, username=username, password=password) @expected_response(expected_status_code=200, expected_format=JSON) - @template_request(method="GET", route="/host_policies/channels", + @template_request(method="GET", route="/host_policies/channels", request_params=[OFFSET, LIMIT, SEARCH, SORT]) def get_list_configuration_channels(self, offset=None, limit=None, search=None, sort=None, **kwargs): return self._base_request(**kwargs) @@ -1001,3 +1001,43 @@ def get_list_hosts_for_channel(self, channel_id, has_active_threats=None, has_ex host_set_id=None, limit=None, offset=None, search=None, sort=None, filter_field=None,**kwargs): return self._base_request(**kwargs) + + +class ProcessTracker(_HX): + + API_BASE_ROUTE = "/hx/api/plugins" + + def __init__(self, hx_host, hx_port=None, verify=False, authenticator=None, username="", password=""): + _HX.__init__(self, hx_host, hx_port=hx_port, verify=verify) + if isinstance(authenticator, Authentication): + self.AUTHENTICATION = authenticator + elif username and password: + self.AUTHENTICATION = Authentication(hx_host=hx_host, hx_port=hx_port, verify=verify, username=username, password=password) + + @expected_response(expected_status_code=200, expected_format=JSON) + @template_request(method="GET", route="/process-tracker/v1/events", + request_params=[OFFSET, LIMIT, SORT, FILTER_FIELD]) + def get_processtracker_events(self, offset=None, limit=None, sort=None, filter_field=None, **kwargs): + return self._base_request(**kwargs) + + +class MessageBus(_HX): + + API_BASE_ROUTE = "/hx/api/services/topic" + + def __init__(self, hx_host, hx_port=None, verify=False, authenticator=None, username="", password=""): + _HX.__init__(self, hx_host, hx_port=hx_port, verify=verify) + if isinstance(authenticator, Authentication): + self.AUTHENTICATION = authenticator + elif username and password: + self.AUTHENTICATION = Authentication(hx_host=hx_host, hx_port=hx_port, verify=verify, username=username, password=password) + + @expected_response(expected_status_code=[200, 204], expected_format=JSON) + @template_request(method="GET", route="/PROCESS_TRACKER", request_headers=[X_OFFSET, X_MAX_MESSAGES, X_POLL_TIMEOUT]) + def get_process_events(self, x_offset=None, x_max_messages=None, x_poll_timeout="45", **kwargs): + return self._base_request(**kwargs) + + @expected_response(expected_status_code=[200, 204], expected_format=JSON) + @template_request(method="GET", route="/HX_ALERTS", request_headers=[X_OFFSET, X_MAX_MESSAGES, X_POLL_TIMEOUT]) + def get_process_alerts(self, x_offset=None, x_max_messages=None, x_poll_timeout="45", **kwargs): + return self._base_request(**kwargs) diff --git a/pyFireEye/utilities/params_helper.py b/pyFireEye/utilities/params_helper.py index 97750dd..b9e641b 100644 --- a/pyFireEye/utilities/params_helper.py +++ b/pyFireEye/utilities/params_helper.py @@ -36,12 +36,21 @@ # Containment STATE_UPDATE_TIME = "state_update_time" +# Headers +X_OFFSET = "X-OFFSET" +X_MAX_MESSAGES = "X-MAX-MESSAGES" +X_POLL_TIMEOUT = "X-POLL-TIMEOUT" # IF YOU NEED TO USE ONE OF THE PARAMETERS IN THIS DICTIONARY, USE THE REPLACEMENT param_arg_map = { + # Params "host_set_id": HOSTS_SET_ID, "category_share_mode": CATEGORY_SHARE_MODE, - "primary_indicator_id": PRIMARY_INDICATOR_ID + "primary_indicator_id": PRIMARY_INDICATOR_ID, + # Headers + "x_offset": X_OFFSET, + "x_max_messages": X_MAX_MESSAGES, + "x_poll_timeout": X_POLL_TIMEOUT } # AX/CM Params diff --git a/pyFireEye/utilities/responses.py b/pyFireEye/utilities/responses.py index d4432f5..717b6a1 100644 --- a/pyFireEye/utilities/responses.py +++ b/pyFireEye/utilities/responses.py @@ -76,9 +76,11 @@ def _json_response(response): data = content.get("data") if isinstance(content, dict) else None if data: del content["data"] - entries = data.get("entries") if data else None + entries = data.get("entries") if isinstance(data, dict) else None if entries: del data["entries"] + if not entries and isinstance(data, list): + entries = data return JsonResponse(message=message, status=status, headers=headers, content=content, data=data, entries=entries) diff --git a/pyFireEye/utilities/wrappers.py b/pyFireEye/utilities/wrappers.py index a786a53..ba32dab 100644 --- a/pyFireEye/utilities/wrappers.py +++ b/pyFireEye/utilities/wrappers.py @@ -21,26 +21,6 @@ tag_pattern = re.compile("<\w[a-zA-Z0-9_]{0,31}>") -def _clean_params(request_params, **kwargs): - """ - This function is used by the template request to make sure that params which do not - follow a usable argument style are properly converted to the correct params - before being passed into the params field for requests - :param request_params: list of request params - :param kwargs: - :return: - """ - replaced_keys = [] - - for k, v in kwargs.items(): - if k in param_arg_map and param_arg_map.get(k) in request_params: - kwargs[param_arg_map.get(k)] = v - replaced_keys.append(k) - - for k in replaced_keys: - del kwargs[k] - - def _route_update(route, **kwargs): """ Used by the template_request decorator to allow the route to be @@ -104,6 +84,16 @@ def _check_args(args, **kwargs): return newdict if not isinstance(args, list): raise TypeError("Expected required to be list") + + replaced_keys = [] + kwargs_copy = kwargs.copy() + for k, v in kwargs_copy.items(): + if k in param_arg_map and param_arg_map.get(k) in args: + kwargs[param_arg_map.get(k)] = v + replaced_keys.append(k) + for k in replaced_keys: + del kwargs[k] + for parameter in args: if kwargs.get(parameter) is not None: newdict[parameter] = kwargs.get(parameter) @@ -164,10 +154,12 @@ def wrap(self, *args, **kwargs): raise UnknownHTTPMethodException(method=method.upper()) kwargs["route"] = _route_update(route=route, **kwargs) kwargs["method"] = method - _clean_params(request_params, **kwargs) kwargs["params"] = _check_args(request_params, **kwargs) kwargs["json"] = _check_args(json_body, **kwargs) kwargs["headers"] = _check_args(request_headers, **kwargs) + # accept_type = kwargs.get("expected_format", None) + # if accept_type == "JSON": + # kwargs["headers"]["Accept"] = "application/json" if require_auth: if self.AUTHENTICATION: @@ -211,6 +203,7 @@ def expected_response(expected_status_code=None, expected_format=None): def decorate(func): @wraps(func) def wrap(*args, **kwargs): + # kwargs["expected_format"] = expected_format response = func(*args, **kwargs) if not isinstance(response, Response): raise ExpectedResponseException(response)