diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
commit | 66cec45960ce1d9c794e9399de15c138acb18aed (patch) | |
tree | 59cd19d69e9d56b7989b080da7c20ef1a3fe2a5a /ansible_collections/ibm/qradar/plugins | |
parent | Initial commit. (diff) | |
download | ansible-66cec45960ce1d9c794e9399de15c138acb18aed.tar.xz ansible-66cec45960ce1d9c794e9399de15c138acb18aed.zip |
Adding upstream version 7.3.0+dfsg.upstream/7.3.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/ibm/qradar/plugins')
21 files changed, 4389 insertions, 0 deletions
diff --git a/ansible_collections/ibm/qradar/plugins/action/__init__.py b/ansible_collections/ibm/qradar/plugins/action/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/action/__init__.py diff --git a/ansible_collections/ibm/qradar/plugins/action/qradar_analytics_rules.py b/ansible_collections/ibm/qradar/plugins/action/qradar_analytics_rules.py new file mode 100644 index 00000000..906cbc9e --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/action/qradar_analytics_rules.py @@ -0,0 +1,226 @@ +# +# Copyright 2022 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# + +""" +The module file for qradar_analytics_rules +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json +from ansible.plugins.action import ActionBase +from ansible.module_utils.connection import Connection +from ansible.module_utils._text import to_text +from ansible.module_utils.six.moves.urllib.parse import quote + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, + remove_unsupported_keys_from_payload_dict, +) +from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( + AnsibleArgSpecValidator, +) +from ansible_collections.ibm.qradar.plugins.modules.qradar_analytics_rules import ( + DOCUMENTATION, +) + + +class ActionModule(ActionBase): + """action module""" + + def __init__(self, *args, **kwargs): + super(ActionModule, self).__init__(*args, **kwargs) + self._supports_async = True + self._result = None + self.api_object = "/api/analytics/rules" + self.api_return = "rules_management" + self.module_return = "qradar_analytics_rules" + self.supported_params = [ + "id", + "name", + "enabled", + "owner", + "fields", + "range", + ] + + def _check_argspec(self): + aav = AnsibleArgSpecValidator( + data=self._task.args, + schema=DOCUMENTATION, + schema_format="doc", + name=self._task.action, + ) + valid, errors, self._task.args = aav.validate() + if not valid: + self._result["failed"] = True + self._result["msg"] = errors + + def search_for_resource(self, qradar_request, search_for_resource=None): + """The fn TC of GATHER operation + :param qradar_request: Qradar connection request + :param search_for_resource: Module input config with either ID, NAME, or RANGE with field input + :rtype: A dict + :returns: dict with module prams transformed having API expected params + """ + if search_for_resource.get("id"): + api_obj_url = self.api_object + "/{0}".format( + search_for_resource["id"] + ) + elif search_for_resource.get("name"): + api_obj_url = self.api_object + "?filter={0}".format( + quote( + 'name="{0}"'.format(to_text(search_for_resource["name"])) + ) + ) + elif search_for_resource.get("range"): + api_obj_url = self.api_object + if search_for_resource.get("fields"): + fields = ",".join(search_for_resource["fields"]) + fields_url = "?fields={0}".format(quote("{0}".format(fields))) + api_obj_url += fields_url + code, rule_source_exists = qradar_request.get(api_obj_url) + if ( + rule_source_exists + and len(rule_source_exists) == 1 + and ( + search_for_resource.get("name") + and not search_for_resource.get("id") + ) + ): + rule_source_exists = rule_source_exists[0] + return rule_source_exists + + def delete_module_api_config(self, qradar_request, module_config_params): + """The fn TC of DELETE operation + :param qradar_request: Qradar connection request + :param module_config_params: Module input config + :rtype: A dict + :returns: Delete output with before and after dict + """ + config = {} + before = {} + after = {} + changed = False + rule_exists = self.search_for_resource( + qradar_request, module_config_params + ) + if rule_exists: + changed = True + before = rule_exists + code, qradar_return_data = qradar_request.delete( + self.api_object + "/{0}".format(rule_exists["id"]) + ) + config.update({"before": before, "after": after}) + else: + config.update({"before": before}) + return config, changed + + def configure_module_api(self, qradar_request, module_config_params): + """The fn TC of MERGE operation + :param qradar_request: Qradar connection request + :param module_config_params: Module input config + :rtype: A dict + :returns: Merge output with before and after dict + """ + config = {} + before = {} + changed = False + + rule_exists = self.search_for_resource( + qradar_request, module_config_params + ) + if rule_exists: + if isinstance(rule_exists, list): + for each in rule_exists: + if each.get("origin") == "OVERRIDE": + rule_exists = each + break + before = rule_exists + module_config_params = utils.remove_empties(module_config_params) + diff = utils.dict_diff(rule_exists, module_config_params) + if diff: + changed = True + qradar_return_data = qradar_request.post_by_path( + self.api_object + "/{0}".format(rule_exists["id"]), + data=json.dumps(diff), + ) + if qradar_return_data[0] >= 200: + config.update( + {"before": before, "after": qradar_return_data[1]} + ) + else: + config.update({"before": before}) + return config, changed + + def run(self, tmp=None, task_vars=None): + self._supports_check_mode = True + self._result = super(ActionModule, self).run(tmp, task_vars) + headers = None + if self._task.args.get("config"): + self._task.args[ + "config" + ] = remove_unsupported_keys_from_payload_dict( + self._task.args["config"], self.supported_params + ) + self._check_argspec() + if self._result.get("failed"): + return self._result + if self._task.args["config"].get("range"): + headers = { + "Content-Type": "application/json", + "Range": "items={0}".format( + self._task.args["config"]["range"] + ), + } + conn = Connection(self._connection.socket_path) + if headers: + conn_request = QRadarRequest( + connection=conn, headers=headers, task_vars=task_vars + ) + else: + conn_request = QRadarRequest(connection=conn, task_vars=task_vars) + if self._task.args["state"] == "gathered": + if self._task.args.get("config"): + self._result["gathered"] = self.search_for_resource( + conn_request, self._task.args["config"] + ) + elif self._task.args["state"] == "merged": + if self._task.args.get("config"): + ( + self._result[self.module_return], + self._result["changed"], + ) = self.configure_module_api( + conn_request, self._task.args["config"] + ) + elif self._task.args["state"] == "deleted": + if self._task.args.get("config"): + ( + self._result[self.module_return], + self._result["changed"], + ) = self.delete_module_api_config( + conn_request, self._task.args["config"] + ) + + return self._result diff --git a/ansible_collections/ibm/qradar/plugins/action/qradar_log_sources_management.py b/ansible_collections/ibm/qradar/plugins/action/qradar_log_sources_management.py new file mode 100644 index 00000000..5d5efcd5 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/action/qradar_log_sources_management.py @@ -0,0 +1,306 @@ +# +# Copyright 2022 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# + +""" +The module file for qradar_log_sources_management +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from copy import copy +import json +from ansible.plugins.action import ActionBase +from ansible.errors import AnsibleActionFail +from ansible.module_utils.connection import Connection +from ansible.module_utils.six.moves.urllib.parse import quote + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, + find_dict_in_list, + list_to_dict, + remove_unsupported_keys_from_payload_dict, +) +from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( + AnsibleArgSpecValidator, +) +from ansible_collections.ibm.qradar.plugins.modules.qradar_log_sources_management import ( + DOCUMENTATION, +) + + +class ActionModule(ActionBase): + """action module""" + + def __init__(self, *args, **kwargs): + super(ActionModule, self).__init__(*args, **kwargs) + self._result = None + self.api_object = ( + "/api/config/event_sources/log_source_management/log_sources" + ) + self.api_object_types = "/api/config/event_sources/log_source_management/log_source_types?filter=" + self.api_object_search = "/api/config/event_sources/log_source_management/log_sources?filter=" + self.api_return = "log_sources_management" + self.module_return = "qradar_log_sources_management" + self.supported_params = [ + "name", + "description", + "type_name", + "type_id", + "identifier", + "protocol_type_id", + "enabled", + "gateway", + "internal", + "target_event_collector_id", + "coalesce_events", + "store_event_payload", + "language_id", + "group_ids", + "requires_deploy", + "status", + "average_eps", + "protocol_parameters", + ] + + def _check_argspec(self): + aav = AnsibleArgSpecValidator( + data=self._task.args, + schema=DOCUMENTATION, + schema_format="doc", + name=self._task.action, + ) + valid, errors, self._task.args = aav.validate() + if not valid: + self._result["failed"] = True + self._result["msg"] = errors + + def set_log_source_values(self, qradar_request, config_params): + # find log source types details + if config_params.get("type_name"): + + api_object = self.api_object_types + "{0}".format( + quote('name="{0}"'.format(config_params["type_name"])) + ) + code, log_source_type_found = qradar_request.get(api_object) + if config_params.get("type_id"): + log_source_type_found = [] + if config_params.get("group_ids"): + del config_params["group_ids"] + elif log_source_type_found and not config_params.get("type_id"): + config_params["type_id"] = log_source_type_found[0]["id"] + config_params.pop("type_name") + else: + raise AnsibleActionFail( + "Incompatible type provided, please consult QRadar Documentation for Log Source Types!" + ) + + if log_source_type_found: + if config_params.get("protocol_type_id"): + found_dict_in_list, _fdil_index = find_dict_in_list( + log_source_type_found[0]["protocol_types"], + "protocol_id", + config_params["protocol_type_id"], + ) + if not found_dict_in_list: + config_params.fail_json( + msg="Incompatible protocol_type_id provided, please consult QRadar Documentation for Log Source Types" + ) + elif log_source_type_found[0].get("protocol_types"): + # Set it to the default as provided by the QRadar Instance + protocol_type_id = 0 + for each in log_source_type_found[0]["protocol_types"]: + if each.get("protocol_id") == 0 and each.get("documented"): + protocol_type_id = 0 + break + elif each.get("documented"): + protocol_type_id = each["protocol_id"] + config_params["protocol_type_id"] = protocol_type_id + + config_params["protocol_parameters"] = [ + { + "id": config_params["protocol_type_id"], + "name": "identifier", + "value": config_params["identifier"], + } + ] + config_params.pop("identifier") + return config_params + + def search_for_resource_name( + self, qradar_request, search_resource_by_names=None + ): + search_result = [] + if isinstance(search_resource_by_names, list): + for each in search_resource_by_names: + each = utils.remove_empties(each) + query_api_object = self.api_object_search + "{0}".format( + quote('name="{0}"'.format(each["name"])) + ) + code, log_source_exists = qradar_request.get(query_api_object) + if log_source_exists and (code >= 200 and code < 300): + search_result.append(log_source_exists[0]) + elif isinstance(search_resource_by_names, str): + query_api_object = self.api_object_search + "{0}".format( + quote('name="{0}"'.format(search_resource_by_names)) + ) + code, log_source_exists = qradar_request.get(query_api_object) + if log_source_exists and (code >= 200 and code < 300): + search_result.append(log_source_exists[0]) + return search_result[0] + else: + code, log_source_exists = qradar_request.get(self.api_object) + if log_source_exists and (code >= 200 and code < 300): + search_result = log_source_exists + return search_result + + def delete_module_api_config(self, qradar_request, module_config_params): + config = {} + before = [] + after = [] + changed = False + for each in module_config_params: + each = utils.remove_empties(each) + log_source_exists = self.search_for_resource_name( + qradar_request, each["name"] + ) + if log_source_exists: + before.append(log_source_exists) + query_object = self.api_object + "/{0}".format( + log_source_exists["id"] + ) + code, qradar_return_data = qradar_request.delete(query_object) + if code >= 200 and code < 300: + changed = True + config.update({"before": before, "after": after}) + else: + config.update({"before": before}) + return config, changed + + def configure_module_api(self, conn_request, module_config_params): + config = {} + before = [] + after = [] + changed = False + temp_request_param = [] + for each in module_config_params: + each = utils.remove_empties(each) + each = self.set_log_source_values(conn_request, each) + search_result = self.search_for_resource_name( + conn_request, each["name"] + ) + if search_result: + if search_result["name"] == each["name"]: + temp_each = copy(each) + temp_search_result = copy(search_result) + list_to_dict(temp_each) + list_to_dict(temp_search_result) + diff = utils.dict_diff(temp_search_result, temp_each) + if diff: + if self._task.args["state"] == "merged": + each = utils.remove_empties( + utils.dict_merge(search_result, each) + ) + temp_request_param.append(each) + elif self._task.args["state"] == "replaced": + query_object = self.api_object + "/{0}".format( + search_result["id"] + ) + code, qradar_return_data = conn_request.delete( + query_object + ) + temp_request_param.append(each) + else: + after.append(search_result) + before.append(search_result) + else: + each = utils.remove_empties(each) + temp_request_param.append(each) + if temp_request_param: + code, response = conn_request.create_update( + self.api_object, + data=json.dumps(temp_request_param), + ) + if code >= 200 and code < 300: + changed = True + search_result = self.search_for_resource_name(conn_request) + for each in temp_request_param: + for every in search_result: + if each["name"] == every["name"]: + after.append(every) + break + elif code >= 400: + raise AnsibleActionFail( + "Failed with http_response: {0} and message: {1}".format( + response["http_response"]["message"], + response["message"], + ) + ) + config.update({"before": before, "after": after}) + else: + config.update({"before": before}) + + return config, changed + + def run(self, tmp=None, task_vars=None): + self._supports_check_mode = True + self._result = super(ActionModule, self).run(tmp, task_vars) + if self._task.args.get("config"): + self._task.args[ + "config" + ] = remove_unsupported_keys_from_payload_dict( + self._task.args["config"], self.supported_params + ) + self._check_argspec() + if self._result.get("failed"): + return self._result + conn = Connection(self._connection.socket_path) + conn_request = QRadarRequest(connection=conn, task_vars=task_vars) + if self._task.args["state"] == "gathered": + if self._task.args.get("config"): + self._result["gathered"] = self.search_for_resource_name( + conn_request, self._task.args["config"] + ) + else: + self._result["gathered"] = conn_request.get(self.api_object) + elif ( + self._task.args["state"] == "merged" + or self._task.args["state"] == "replaced" + ): + if self._task.args.get("config"): + ( + self._result[self.module_return], + self._result["changed"], + ) = self.configure_module_api( + conn_request, self._task.args["config"] + ) + elif self._task.args["state"] == "deleted": + if self._task.args.get("config"): + ( + self._result[self.module_return], + self._result["changed"], + ) = self.delete_module_api_config( + conn_request, self._task.args["config"] + ) + + return self._result diff --git a/ansible_collections/ibm/qradar/plugins/httpapi/qradar.py b/ansible_collections/ibm/qradar/plugins/httpapi/qradar.py new file mode 100644 index 00000000..004c7815 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/httpapi/qradar.py @@ -0,0 +1,76 @@ +# (c) 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +author: Ansible Security Team (@ansible-security) +name: qradar +short_description: HttpApi Plugin for IBM QRadar +description: + - This HttpApi plugin provides methods to connect to IBM QRadar over a + HTTP(S)-based api. +version_added: "1.0.0" +""" + +import json + +from ansible.module_utils.basic import to_text +from ansible.module_utils.six.moves.urllib.error import HTTPError +from ansible_collections.ansible.netcommon.plugins.plugin_utils.httpapi_base import ( + HttpApiBase, +) +from ansible.module_utils.connection import ConnectionError +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + BASE_HEADERS, +) + + +class HttpApi(HttpApiBase): + def send_request(self, request_method, path, payload=None, headers=None): + headers = headers if headers else BASE_HEADERS + + try: + self._display_request(request_method) + response, response_data = self.connection.send( + path, payload, method=request_method, headers=headers + ) + value = self._get_response_value(response_data) + + return response.getcode(), self._response_to_json(value) + except HTTPError as e: + error = json.loads(e.read()) + return e.code, error + + def _display_request(self, request_method): + self.connection.queue_message( + "vvvv", + "Web Services: %s %s" % (request_method, self.connection._url), + ) + + def _get_response_value(self, response_data): + return to_text(response_data.getvalue()) + + def _response_to_json(self, response_text): + try: + return json.loads(response_text) if response_text else {} + # JSONDecodeError only available on Python 3.5+ + except ValueError: + raise ConnectionError("Invalid JSON response: %s" % response_text) + + def update_auth(self, response, response_text): + cookie = response.info().get("Set-Cookie") + # Set the 'SEC' header + if "SEC" in cookie: + return {"SEC": cookie.split(";")[0].split("=")[-1]} + + return None + + def logout(self): + self.send_request("POST", "/auth/logout") + + # Clean up tokens + self.connection._auth = None diff --git a/ansible_collections/ibm/qradar/plugins/module_utils/qradar.py b/ansible_collections/ibm/qradar/plugins/module_utils/qradar.py new file mode 100644 index 00000000..e1569863 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/module_utils/qradar.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +from ansible.module_utils.urls import CertificateError +from ansible.module_utils.six.moves.urllib.parse import quote_plus +from ansible.module_utils.connection import ConnectionError +from ansible.module_utils.connection import Connection +from ansible.module_utils._text import to_text +from ansible.module_utils.six import iteritems +from copy import copy +import json + + +BASE_HEADERS = {"Content-Type": "application/json", "Version": "9.1"} + + +def find_dict_in_list(some_list, key, value): + text_type = False + try: + to_text(value) + text_type = True + except TypeError: + pass + for some_dict in some_list: + if key in some_dict: + if text_type: + if to_text(some_dict[key]).strip() == to_text(value).strip(): + return some_dict, some_list.index(some_dict) + else: + if some_dict[key] == value: + return some_dict, some_list.index(some_dict) + return None + + +def set_offense_values(module, qradar_request): + if module.params["closing_reason"]: + found_closing_reason = qradar_request.get_by_path( + "api/siem/offense_closing_reasons?filter={0}".format( + quote_plus( + 'text="{0}"'.format(module.params["closing_reason"]) + ) + ) + ) + if found_closing_reason: + module.params["closing_reason_id"] = found_closing_reason[0]["id"] + else: + module.fail_json( + "Unable to find closing_reason text: {0}".format( + module.params["closing_reason"] + ) + ) + + if module.params["status"]: + module.params["status"] = module.params["status"].upper() + + +def remove_unsupported_keys_from_payload_dict(payload, supported_key_list): + """The fn to remove the unsupported keys from payload + :param payload: Payload from response + :param supported_key_list: Module supported params + :rtype: A dict + :returns: payload with only supported and module expected params + """ + temp_payload = [] + for each in payload: + temp_dict = copy(each) + if isinstance(each, dict): + for every_key in each.keys(): + if every_key not in supported_key_list: + temp_dict.pop(every_key) + temp_payload.append(temp_dict) + if temp_payload: + return temp_payload + return payload + + +def list_to_dict(input_dict): + """The fn to convert list of dict to dict + :param input_dict: input list having list of dict + :rtype: A dict + :returns: dict with converted all list to dict type + """ + if isinstance(input_dict, dict): + for k, v in iteritems(input_dict): + if isinstance(v, dict): + list_to_dict(v) + elif isinstance(v, list): + temp_dict = {} + for each in v: + if isinstance(each, dict): + if each.get("id") or each.get("id") == 0: + each.pop("id") + each_key_values = "_".join( + [str(x) for x in each.values()] + ) + temp_dict.update({each_key_values: each}) + input_dict[k] = temp_dict + + +class QRadarRequest(object): + def __init__( + self, + module=None, + connection=None, + headers=None, + not_rest_data_keys=None, + task_vars=None, + ): + self.module = module + if module: + # This will be removed, once all of the available modules + # are moved to use action plugin design, as otherwise test + # would start to complain without the implementation. + self.connection = Connection(self.module._socket_path) + elif connection: + self.connection = connection + try: + self.connection.load_platform_plugins("ibm.qradar.qradar") + self.connection.set_options(var_options=task_vars) + except ConnectionError: + raise + # This allows us to exclude specific argspec keys from being included by + # the rest data that don't follow the qradar_* naming convention + if not_rest_data_keys: + self.not_rest_data_keys = not_rest_data_keys + else: + self.not_rest_data_keys = [] + self.not_rest_data_keys.append("validate_certs") + self.headers = headers if headers else BASE_HEADERS + + def _httpapi_error_handle(self, method, uri, payload=None): + # FIXME - make use of handle_httperror(self, exception) where applicable + # https://docs.ansible.com/ansible/latest/network/dev_guide/developing_plugins_network.html#developing-plugins-httpapi + code = 99999 + response = {} + try: + code, response = self.connection.send_request( + method, uri, payload=payload, headers=self.headers + ) + except ConnectionError as e: + self.module.fail_json( + msg="connection error occurred: {0}".format(e) + ) + except CertificateError as e: + self.module.fail_json( + msg="certificate error occurred: {0}".format(e) + ) + except ValueError as e: + try: + self.module.fail_json( + msg="certificate not found: {0}".format(e) + ) + except AttributeError: + pass + + if code == 404: + if ( + to_text("Object not found") in to_text(response) + or to_text("Could not find object") in to_text(response) + or to_text("No offense was found") in to_text(response) + ): + return {} + if to_text("The rule does not exist.") in to_text( + response["description"] + ): + return code, {} + + if code == 409: + if "code" in response: + if response["code"] in [1002, 1004]: + # https://www.ibm.com/support/knowledgecenter/SS42VS_7.3.1/com.ibm.qradar.doc/9.2--staged_config-deploy_status-POST.html + # Documentation says we should get 1002, but I'm getting 1004 from QRadar + return response + else: + self.module.fail_json( + msg="qradar httpapi returned error {0} with message {1}".format( + code, response + ) + ) + elif not (code >= 200 and code < 300): + try: + self.module.fail_json( + msg="qradar httpapi returned error {0} with message {1}".format( + code, response + ) + ) + except AttributeError: + pass + + return code, response + + def get(self, url, **kwargs): + return self._httpapi_error_handle("GET", url, **kwargs) + + def put(self, url, **kwargs): + return self._httpapi_error_handle("PUT", url, **kwargs) + + def post(self, url, **kwargs): + return self._httpapi_error_handle("POST", url, **kwargs) + + def patch(self, url, **kwargs): + return self._httpapi_error_handle("PATCH", url, **kwargs) + + def delete(self, url, **kwargs): + return self._httpapi_error_handle("DELETE", url, **kwargs) + + def get_data(self): + """ + Get the valid fields that should be passed to the REST API as urlencoded + data so long as the argument specification to the module follows the + convention: + - the key to the argspec item does not start with qradar_ + - the key does not exist in the not_data_keys list + """ + try: + qradar_data = {} + for param in self.module.params: + if (self.module.params[param]) is not None and ( + param not in self.not_rest_data_keys + ): + qradar_data[param] = self.module.params[param] + return qradar_data + + except TypeError as e: + self.module.fail_json( + msg="invalid data type provided: {0}".format(e) + ) + + def post_by_path(self, rest_path, data=None): + """ + POST with data to path + """ + if data is None: + data = json.dumps(self.get_data()) + elif data is False: + # Because for some reason some QRadar REST API endpoint use the + # query string to modify state + return self.post("/{0}".format(rest_path)) + return self.post("/{0}".format(rest_path), payload=data) + + def create_update(self, rest_path, data=None): + """ + Create or Update a file/directory monitor data input in qradar + """ + if data is None: + data = json.dumps(self.get_data()) + # return self.post("/{0}".format(rest_path), payload=data) + return self.patch("/{0}".format(rest_path), payload=data) # PATCH diff --git a/ansible_collections/ibm/qradar/plugins/modules/deploy.py b/ansible_collections/ibm/qradar/plugins/modules/deploy.py new file mode 100644 index 00000000..dc41acd9 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/deploy.py @@ -0,0 +1,89 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: deploy +short_description: Trigger a qradar configuration deployment +description: + - This module allows for INCREMENTAL or FULL deployments +version_added: "1.0.0" +options: + type: + description: + - Type of deployment + required: false + type: str + choices: + - "INCREMENTAL" + - "FULL" + default: "INCREMENTAL" +notes: + - This module does not support check mode because the QRadar REST API does not offer stateful inspection of configuration deployments + +author: "Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security>" +""" + +EXAMPLES = """ +- name: run an incremental deploy + ibm.qradar.deploy: + type: INCREMENTAL +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, +) + + +def main(): + + argspec = dict( + type=dict( + choices=["INCREMENTAL", "FULL"], + required=False, + default="INCREMENTAL", + ) + ) + + module = AnsibleModule(argument_spec=argspec, supports_check_mode=False) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["state", "type_name", "identifier"], + ) + + qradar_return_data = qradar_request.post_by_path( + "api/staged_config/deploy_status" + ) + + if "message" in qradar_return_data and ( + to_text("No changes to deploy") + in to_text(qradar_return_data["message"]) + ): + module.exit_json( + msg="No changes to deploy", + qradar_return_data=qradar_return_data, + changed=False, + ) + else: + module.exit_json( + msg="Successfully initiated {0} deployment.".format( + module.params["type"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/log_source_management.py b/ansible_collections/ibm/qradar/plugins/modules/log_source_management.py new file mode 100644 index 00000000..fa793a59 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/log_source_management.py @@ -0,0 +1,263 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: log_source_management +short_description: Manage Log Sources in QRadar +description: + - This module allows for addition, deletion, or modification of Log Sources in QRadar +version_added: "1.0.0" +deprecated: + alternative: qradar_log_sources_management + why: Newer and updated modules released with more functionality. + removed_at_date: '2024-09-01' +options: + name: + description: + - Name of Log Source + required: true + type: str + state: + description: + - Add or remove a log source. + required: true + choices: [ "present", "absent" ] + type: str + type_name: + description: + - Type of resource by name + required: false + type: str + type_id: + description: + - Type of resource by id, as defined in QRadar Log Source Types Documentation + required: false + type: int + protocol_type_id: + description: + - Type of protocol by id, as defined in QRadar Log Source Types Documentation + required: false + type: int + identifier: + description: + - Log Source Identifier (Typically IP Address or Hostname of log source) + required: true + type: str + description: + description: + - Description of log source + required: true + type: str + +notes: + - Either C(type) or C(type_id) is required + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + +EXAMPLES = """ +- name: Add a snort log source to IBM QRadar + ibm.qradar.log_source_management: + name: "Snort logs" + type_name: "Snort Open Source IDS" + state: present + description: "Snort IDS remote logs from rsyslog" + identifier: "192.168.1.101" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, + find_dict_in_list, +) + +import json + + +def set_log_source_values(module, qradar_request): + if module.params["type_name"]: + code, query_response = qradar_request.get( + "/api/config/event_sources/log_source_management/log_source_types?filter={0}".format( + quote('name="{0}"'.format(module.params["type_name"])) + ) + ) + log_source_type_found = query_response[0] + if module.params["type_id"]: + code, query_response = qradar_request.get( + "/api/config/event_sources/log_source_management/log_source_types?filter={0}".format( + quote('name="{0}"'.format(module.params["type_name"])) + ) + ) + code, log_source_type_found = query_response[0] + if log_source_type_found: + if not module.params["type_id"]: + module.params["type_id"] = log_source_type_found["id"] + else: + module.fail_json( + msg="Incompatible type provided, please consult QRadar Documentation for Log Source Types" + ) + + if module.params["protocol_type_id"]: + found_dict_in_list, _fdil_index = find_dict_in_list( + log_source_type_found["protocol_types"], + "protocol_id", + module.params["protocol_type_id"], + ) + if not found_dict_in_list: + module.fail_json( + msg="Incompatible protocol_type_id provided, please consult QRadar Documentation for Log Source Types" + ) + else: + # Set it to the default as provided by the QRadar Instance + module.params["protocol_type_id"] = log_source_type_found[ + "protocol_types" + ][0]["protocol_id"] + + module.params["protocol_parameters"] = [ + { + "id": module.params["protocol_type_id"], + "name": "identifier", + "value": module.params["identifier"], + } + ] + + +def main(): + + argspec = dict( + name=dict(required=True, type="str"), + state=dict(choices=["present", "absent"], required=True), + type_name=dict(required=False, type="str"), + type_id=dict(required=False, type="int"), + identifier=dict(required=True, type="str"), + protocol_type_id=dict(required=False, type="int"), + description=dict(required=True, type="str"), + ) + + module = AnsibleModule( + argument_spec=argspec, + required_one_of=[("type_name", "type_id")], + mutually_exclusive=[("type_name", "type_id")], + supports_check_mode=True, + ) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["state", "type_name", "identifier"], + ) + + code, log_source_exists = qradar_request.get( + "/api/config/event_sources/log_source_management/log_sources?filter={0}".format( + quote('name="{0}"'.format(module.params["name"])) + ) + ) + + if log_source_exists: + + if module.params["state"] == "present": + ( + existing_log_source_protocol_identifier, + _elspi_index, + ) = find_dict_in_list( + log_source_exists[0]["protocol_parameters"], + "name", + "identifier", + ) + + set_log_source_values(module, qradar_request) + + comparison_map = [ + existing_log_source_protocol_identifier["value"] + == module.params["identifier"], + log_source_exists[0]["name"] == module.params["name"], + log_source_exists[0]["type_id"] == module.params["type_id"], + to_text(log_source_exists[0]["description"]) + == to_text(module.params["description"]), + ] + + if all(comparison_map): + module.exit_json(changed=False, msg="Nothing to do.") + else: + log_source_exists[0]["protocol_parameters"][ + _elspi_index + ] = module.params["protocol_parameters"][0] + log_source_exists[0]["name"] = module.params["name"] + log_source_exists[0]["type_id"] = module.params["type_id"] + log_source_exists[0]["description"] = module.params[ + "description" + ] + if module.check_mode: + qradar_return_data = { + "EMPTY": "IN CHECK MODE, NO TRANSACTION TOOK PLACE" + } + else: + code, qradar_return_data = qradar_request.create_update( + "api/config/event_sources/log_source_management/log_sources", + data=json.dumps(log_source_exists), + ) + + module.exit_json( + msg="Successfully updated log source: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + + if module.params["state"] == "absent": + if module.check_mode: + qradar_return_data = { + "EMPTY": "IN CHECK MODE, NO TRANSACTION TOOK PLACE" + } + else: + code, qradar_return_data = qradar_request.delete( + "/api/config/event_sources/log_source_management/log_sources/{0}".format( + log_source_exists[0]["id"] + ) + ) + + module.exit_json( + msg="Successfully deleted log source: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + if module.params["state"] == "present": + set_log_source_values(module, qradar_request) + if module.check_mode: + qradar_return_data = { + "EMPTY": "IN CHECK MODE, NO TRANSACTION TOOK PLACE" + } + else: + code, qradar_return_data = qradar_request.create_update( + "api/config/event_sources/log_source_management/log_sources", + data=json.dumps([qradar_request.get_data()]), + ) + + module.exit_json( + msg="Successfully created log source: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + + if module.params["state"] == "absent": + module.exit_json(changed=False, msg="Nothing to do.") + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/offense_action.py b/ansible_collections/ibm/qradar/plugins/modules/offense_action.py new file mode 100644 index 00000000..784f1e7b --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/offense_action.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: offense_action +short_description: Take action on a QRadar Offense +description: + - This module allows to assign, protect, follow up, set status, and assign closing reason to QRadar Offenses +version_added: "1.0.0" +options: + id: + description: + - ID of Offense + required: true + type: int + status: + description: + - One of "open", "hidden" or "closed". (Either all lower case or all caps) + required: false + choices: [ "open", "OPEN", "hidden", "HIDDEN", "closed", "CLOSED" ] + type: str + assigned_to: + description: + - Assign to an user, the QRadar username should be provided + required: false + type: str + closing_reason: + description: + - Assign a predefined closing reason here, by name. + required: false + type: str + closing_reason_id: + description: + - Assign a predefined closing reason here, by id. + required: false + type: int + follow_up: + description: + - Set or unset the flag to follow up on a QRadar Offense + required: false + type: bool + protected: + description: + - Set or unset the flag to protect a QRadar Offense + required: false + type: bool + +notes: + - Requires one of C(name) or C(id) be provided + - Only one of C(closing_reason) or C(closing_reason_id) can be provided + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + +""" +# FIXME - WOULD LIKE TO QUERY BY NAME BUT HOW TO ACCOMPLISH THAT IS NON-OBVIOUS +# name: +# description: +# - Name of Offense +# required: true +# type: str +""" + +EXAMPLES = """ +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, + set_offense_values, +) + + +def main(): + + argspec = dict( + # name=dict(required=False, type='str'), + # id=dict(required=False, type='str'), + id=dict(required=True, type="int"), + assigned_to=dict(required=False, type="str"), + closing_reason=dict(required=False, type="str"), + closing_reason_id=dict(required=False, type="int"), + follow_up=dict(required=False, type="bool"), + protected=dict(required=False, type="bool"), + status=dict( + required=False, + choices=["open", "OPEN", "hidden", "HIDDEN", "closed", "CLOSED"], + type="str", + ), + ) + + module = AnsibleModule( + argument_spec=argspec, + # required_one_of=[ + # ('name', 'id',), + # ], + mutually_exclusive=[("closing_reason", "closing_reason_id")], + supports_check_mode=True, + ) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["name", "id", "assigned_to", "closing_reason"], + ) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME + # found_offense = qradar_request.get('/api/siem/offenses?filter={0}'.format(module.params['name'])) + + code, found_offense = qradar_request.get( + "/api/siem/offenses/{0}".format(module.params["id"]) + ) + + if found_offense: + set_offense_values(module, qradar_request) + + post_strs = [] + + if module.params["status"] and ( + to_text(found_offense["status"]) + != to_text(module.params["status"]) + ): + post_strs.append( + "status={0}".format(to_text(module.params["status"])) + ) + + if module.params["assigned_to"] and ( + to_text(found_offense["assigned_to"]) + != to_text(module.params["assigned_to"]) + ): + post_strs.append( + "assigned_to={0}".format(module.params["assigned_to"]) + ) + + if module.params["closing_reason_id"] and ( + found_offense["closing_reason_id"] + != module.params["closing_reason_id"] + ): + post_strs.append( + "closing_reason_id={0}".format( + module.params["closing_reason_id"] + ) + ) + + if module.params["follow_up"] and ( + found_offense["follow_up"] != module.params["follow_up"] + ): + post_strs.append( + "follow_up={0}".format(module.params["follow_up"]) + ) + + if module.params["protected"] and ( + found_offense["protected"] != module.params["protected"] + ): + post_strs.append( + "protected={0}".format(module.params["protected"]) + ) + + if post_strs: + if module.check_mode: + module.exit_json( + msg="A change would have been made but was not because of Check Mode.", + changed=True, + ) + + qradar_return_data = qradar_request.post_by_path( + "api/siem/offenses/{0}?{1}".format( + module.params["id"], "&".join(post_strs) + ) + ) + # FIXME - handle the scenario in which we can search by name and this isn't a required param anymore + module.exit_json( + msg="Successfully updated Offense ID: {0}".format( + module.params["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + module.exit_json( + msg="No changes necessary. Nothing to do.", changed=False + ) + else: + # FIXME - handle the scenario in which we can search by name and this isn't a required param anymore + module.fail_json( + msg="Unable to find Offense ID: {0}".format(module.params["id"]) + ) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/offense_info.py b/ansible_collections/ibm/qradar/plugins/modules/offense_info.py new file mode 100644 index 00000000..1ead8a1a --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/offense_info.py @@ -0,0 +1,216 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: offense_info +short_description: Obtain information about one or many QRadar Offenses, with filter options +description: + - This module allows to obtain information about one or many QRadar Offenses, with filter options +version_added: "1.0.0" +options: + id: + description: + - Obtain only information of the Offense with provided ID + required: false + type: int + name: + description: + - Obtain only information of the Offense that matches the provided name + required: false + type: str + status: + description: + - Obtain only information of Offenses of a certain status + required: false + choices: [ "open", "OPEN", "hidden", "HIDDEN", "closed", "CLOSED" ] + default: "open" + type: str + assigned_to: + description: + - Obtain only information of Offenses assigned to a certain user + required: false + type: str + closing_reason: + description: + - Obtain only information of Offenses that were closed by a specific closing reason + required: false + type: str + closing_reason_id: + description: + - Obtain only information of Offenses that were closed by a specific closing reason ID + required: false + type: int + follow_up: + description: + - Obtain only information of Offenses that are marked with the follow up flag + required: false + type: bool + protected: + description: + - Obtain only information of Offenses that are protected + required: false + type: bool +notes: + - You may provide many filters and they will all be applied, except for C(id) + as that will return only + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + + +# FIXME - provide correct example here +RETURN = """ +offenses: + description: Information + returned: always + type: list + elements: dict + contains: + qradar_offenses: + description: IBM QRadar Offenses found based on provided filters + returned: always + type: complex + contains: + source: + description: Init system of the service. One of C(systemd), C(sysv), C(upstart). + returned: always + type: str + sample: sysv + state: + description: State of the service. Either C(running), C(stopped), or C(unknown). + returned: always + type: str + sample: running + status: + description: State of the service. Either C(enabled), C(disabled), or C(unknown). + returned: systemd systems or RedHat/SUSE flavored sysvinit/upstart + type: str + sample: enabled + name: + description: Name of the service. + returned: always + type: str + sample: arp-ethers.service +""" + + +EXAMPLES = """ +- name: Get list of all currently OPEN IBM QRadar Offenses + ibm.qradar.offense_info: + status: OPEN + register: offense_list + +- name: display offense information for debug purposes + debug: + var: offense_list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, + find_dict_in_list, + set_offense_values, +) + + +def main(): + + argspec = dict( + id=dict(required=False, type="int"), + name=dict(required=False, type="str"), + assigned_to=dict(required=False, type="str"), + closing_reason=dict(required=False, type="str"), + closing_reason_id=dict(required=False, type="int"), + follow_up=dict(required=False, type="bool", default=None), + protected=dict(required=False, type="bool", default=None), + status=dict( + required=False, + choices=["open", "OPEN", "hidden", "HIDDEN", "closed", "CLOSED"], + default="open", + type="str", + ), + ) + + module = AnsibleModule( + argument_spec=argspec, + mutually_exclusive=[("closing_reason", "closing_reason_id")], + supports_check_mode=True, + ) + + qradar_request = QRadarRequest(module) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME NATIVELY VIA REST API (DOESN'T EXIST YET) + # found_offense = qradar_request.get('/api/siem/offenses?filter={0}'.format(module.params['name'])) + + set_offense_values(module, qradar_request) + + if module.params["id"]: + code, offenses = qradar_request.get( + "/api/siem/offenses/{0}".format(module.params["id"]) + ) + + else: + query_strs = [] + + if module.params["status"]: + query_strs.append( + quote("status={0}".format(to_text(module.params["status"]))) + ) + + if module.params["assigned_to"]: + query_strs.append( + quote("assigned_to={0}".format(module.params["assigned_to"])) + ) + + if module.params["closing_reason_id"]: + query_strs.append( + quote( + "closing_reason_id={0}".format( + module.params["closing_reason_id"] + ) + ) + ) + + if module.params["follow_up"] is not None: + query_strs.append( + quote("follow_up={0}".format(module.params["follow_up"])) + ) + + if module.params["protected"] is not None: + query_strs.append( + quote("protected={0}".format(module.params["protected"])) + ) + + if query_strs: + code, offenses = qradar_request.get( + "/api/siem/offenses?filter={0}".format("&".join(query_strs)) + ) + else: + code, offenses = qradar_request.get("/api/siem/offenses") + + if module.params["name"]: + named_offense = find_dict_in_list( + offenses, "description", module.params["name"] + ) + if named_offense: + offenses = named_offense + else: + offenses = [] + + module.exit_json(offenses=offenses, changed=False) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/offense_note.py b/ansible_collections/ibm/qradar/plugins/modules/offense_note.py new file mode 100644 index 00000000..53b6ec95 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/offense_note.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: offense_note +short_description: Create or update a QRadar Offense Note +description: + - This module allows to create a QRadar Offense note +version_added: "1.0.0" +options: + id: + description: + - Offense ID to operate on + required: true + type: int + note_text: + description: The note's text contents + required: true + type: str + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + +""" +# FIXME - WOULD LIKE TO QUERY BY NAME BUT HOW TO ACCOMPLISH THAT IS NON-OBVIOUS +# offense_name: +# description: +# - Name of Offense +# required: true +# type: str + +# FIXME - WOULD LIKE TO MANAGE STATE +# state: +# description: Define state of the note: present or absent +# required: false +# choices: ["present", "absent"] +# default: "present" +""" + +EXAMPLES = """ +- name: Add a note to QRadar Offense ID 1 + ibm.qradar.offense_note: + id: 1 + note_text: This an example note entry that should be made on offense id 1 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, +) + + +def set_offense_values(module, qradar_request): + if module.params["closing_reason"]: + code, found_closing_reason = qradar_request.get( + "/api/siem/offense_closing_reasons?filter={0}".format( + quote('text="{0}"'.format(module.params["closing_reason"])) + ) + ) + if found_closing_reason: + module.params["closing_reason_id"] = found_closing_reason[0]["id"] + else: + module.fail_json( + "Unable to find closing_reason text: {0}".format( + module.params["closing_reason"] + ) + ) + + if module.params["status"]: + module.params["status"] = module.params["status"].upper() + + +def main(): + + argspec = dict( + # state=dict(required=False, choices=["present", "absent"], type='str', default="present"), + id=dict(required=True, type="int"), + note_text=dict(required=True, type="str"), + ) + + module = AnsibleModule(argument_spec=argspec, supports_check_mode=True) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["state", "id"], + ) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME + # found_offense = qradar_request.get('/api/siem/offenses?filter={0}'.format(module.params['name'])) + # FIXME - once this is sorted, add it to module_utils + + code, found_notes = qradar_request.get( + "/api/siem/offenses/{0}/notes?filter={1}".format( + module.params["id"], + quote('note_text="{0}"'.format(module.params["note_text"])), + ) + ) + + # if module.params['state'] == 'present': + + if found_notes: + # The note we want exists either by ID or by text name, verify + + note = found_notes[0] + if note["note_text"] == module.params["note_text"]: + module.exit_json( + msg="No changes necessary. Nothing to do.", changed=False + ) + else: + if module.check_mode: + module.exit_json( + msg="A change would have occured but did not because Check Mode", + changed=True, + ) + + qradar_return_data = qradar_request.post_by_path( + "api/siem/offenses/{0}/notes?note_text={1}".format( + module.params["id"], + quote("{0}".format(module.params["note_text"])), + ), + data=False, + ) + module.exit_json( + msg="Successfully created Offense Note ID: {0}".format( + qradar_return_data["id"] + ), + qradar_return_data=qradar_return_data, + changed=False, + ) + + else: + if module.check_mode: + module.exit_json( + msg="A change would have occured but did not because Check Mode", + changed=True, + ) + + qradar_return_data = qradar_request.post_by_path( + "api/siem/offenses/{0}/notes?note_text={1}".format( + module.params["id"], + quote("{0}".format(module.params["note_text"])), + ), + data=False, + ) + module.exit_json( + msg="Successfully created Offense Note ID: {0}".format( + qradar_return_data["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + + module.exit_json(msg="No changes necessary. Nothing to do.", changed=False) + + # FIXME FIXME FIXME - can we actually delete these via the REST API? + # if module.params['state'] == 'absent': + # if not found_notes: + # module.exit_json(msg="No changes necessary. Nothing to do.", changed=False) + # else: + # if module.check_mode: + # module.exit_json(msg="A change would have occured but did not because Check Mode", changed=True) + # # FIXME: fix the POST here to actually delete + # qradar_return_data = qradar_request.post_by_path( + # 'api/siem/offenses/{0}/notes?note_text={1}'.format( + # module.params['id'], + # quote("{0}".format(module.params['note_text'])), + # ), + # data=False + # ) + # module.exit_json( + # msg="Successfully created Offense Note ID: {0}".format(qradar_return_data['id']), + # qradar_return_data=qradar_return_data, + # changed=True + # ) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/qradar_analytics_rules.py b/ansible_collections/ibm/qradar/plugins/modules/qradar_analytics_rules.py new file mode 100644 index 00000000..8f65069f --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/qradar_analytics_rules.py @@ -0,0 +1,279 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: qradar_analytics_rules +short_description: Qradar Analytics Rules Management resource module +description: + - This module allows for modification, deletion, and checking of Analytics Rules in QRadar +version_added: "2.1.0" +options: + config: + description: A dictionary of Qradar Analytics Rules options + type: dict + suboptions: + id: + description: The sequence ID of the rule. + type: int + name: + description: The name of the rule. + type: str + enabled: + description: Check if the rule is enabled + type: bool + owner: + description: Manage ownership of a QRadar Rule + type: str + fields: + description: + - List of params filtered from the Rule config + - NOTE, this param is valid only via state GATHERED. + type: list + elements: str + choices: + - average_capacity + - base_capacity + - base_host_id + - capacity_timestamp + - creation_date + - enabled + - id + - identifier + - linked_rule_identifier + - modification_date + - name + - origin + - owner + - type + range: + description: + - Parameter to restrict the number of elements that + are returned in the list to a specified range. + - NOTE, this param is valid only via state GATHERED. + type: str + state: + description: + - The state the configuration should be left in + - The state I(gathered) will get the module API configuration from the device + and transform it into structured data in the format as per the module argspec + and the value is returned in the I(gathered) key within the result. + type: str + choices: + - merged + - gathered + - deleted + +author: Ansible Security Automation Team (@justjais) <https://github.com/ansible-security> +""" + +EXAMPLES = """ + +# Using MERGED state +# ------------------- + +- name: DISABLE Rule 'Ansible Example DDoS Rule' + ibm.qradar.qradar_analytics_rules: + config: + name: 'Ansible Example DDOS Rule' + enabled: false + state: merged + +# RUN output: +# ----------- + +# qradar_analytics_rules: +# after: +# average_capacity: null +# base_capacity: null +# base_host_id: null +# capacity_timestamp: null +# creation_date: 1658929682568 +# enabled: false +# id: 100443 +# identifier: ae5a1268-02a0-4976-84c5-dbcbcf854b9c +# linked_rule_identifier: null +# modification_date: 1658929682567 +# name: Ansible Example DDOS Rule +# origin: USER +# owner: admin +# type: EVENT +# before: +# average_capacity: null +# base_capacity: null +# base_host_id: null +# capacity_timestamp: null +# creation_date: 1658929682568 +# enabled: true +# id: 100443 +# identifier: ae5a1268-02a0-4976-84c5-dbcbcf854b9c +# linked_rule_identifier: null +# modification_date: 1658929682567 +# name: Ansible Example DDOS Rule +# origin: USER +# owner: admin +# type: EVENT + + +# Using GATHERED state +# -------------------- + +- name: Get information about the Rule named "Ansible Example DDOS Rule" + ibm.qradar.qradar_analytics_rules: + config: + name: "Ansible Example DDOS Rule" + state: gathered + +# RUN output: +# ----------- + +# gathered: +# average_capacity: null +# base_capacity: null +# base_host_id: null +# capacity_timestamp: null +# creation_date: 1658918848694 +# enabled: true +# id: 100443 +# identifier: d6d37942-ba28-438f-b909-120df643a992 +# linked_rule_identifier: null +# modification_date: 1658918848692 +# name: Ansible Example DDOS Rule +# origin: USER +# owner: admin +# type: EVENT + +- name: Get information about the Rule with ID 100443 + ibm.qradar.qradar_analytics_rules: + config: + id: 100443 + state: gathered + +# RUN output: +# ----------- + +# gathered: +# average_capacity: null +# base_capacity: null +# base_host_id: null +# capacity_timestamp: null +# creation_date: 1658918848694 +# enabled: true +# id: 100443 +# identifier: d6d37942-ba28-438f-b909-120df643a992 +# linked_rule_identifier: null +# modification_date: 1658918848692 +# name: Ansible Example DDOS Rule +# origin: USER +# owner: admin +# type: EVENT + +- name: TO Get information about the Rule ID with a range + ibm.qradar.qradar_analytics_rules: + config: + range: 100300-100500 + fields: + - name + - origin + - owner + state: gathered + +# RUN output: +# ----------- + +# gathered: +# - name: Devices with High Event Rates +# origin: SYSTEM +# owner: admin +# - name: Excessive Database Connections +# origin: SYSTEM +# owner: admin +# - name: 'Anomaly: Excessive Firewall Accepts Across Multiple Hosts' +# origin: SYSTEM +# owner: admin +# - name: Excessive Firewall Denies from Single Source +# origin: SYSTEM +# owner: admin +# - name: 'AssetExclusion: Exclude DNS Name By IP' +# origin: SYSTEM +# owner: admin +# - name: 'AssetExclusion: Exclude DNS Name By MAC Address' +# origin: SYSTEM +# owner: admin + +- name: Delete custom Rule by NAME + ibm.qradar.qradar_analytics_rules: + config: + name: 'Ansible Example DDOS Rule' + state: deleted + +# RUN output: +# ----------- + +# qradar_analytics_rules: +# after: {} +# before: +# average_capacity: null +# base_capacity: null +# base_host_id: null +# capacity_timestamp: null +# creation_date: 1658929431239 +# enabled: true +# id: 100444 +# identifier: 3c2cbd9d-d141-49fc-b5d5-29009a9b5308 +# linked_rule_identifier: null +# modification_date: 1658929431238 +# name: Ansible Example DDOS Rule +# origin: USER +# owner: admin +# type: EVENT + +# Using DELETED state +# ------------------- + +- name: Delete custom Rule by ID + ibm.qradar.qradar_analytics_rules: + config: + id: 100443 + state: deleted + +# RUN output: +# ----------- + +# qradar_analytics_rules: +# after: {} +# before: +# average_capacity: null +# base_capacity: null +# base_host_id: null +# capacity_timestamp: null +# creation_date: 1658929431239 +# enabled: true +# id: 100443 +# identifier: 3c2cbd9d-d141-49fc-b5d5-29009a9b5308 +# linked_rule_identifier: null +# modification_date: 1658929431238 +# name: Ansible Example DDOS Rule +# origin: USER +# owner: admin +# type: EVENT + +""" + +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: dict + sample: The configuration returned will always be in the same format of the parameters above. +after: + description: The configuration as structured data after module completion. + returned: when changed + type: dict + sample: The configuration returned will always be in the same format of the parameters above. +""" diff --git a/ansible_collections/ibm/qradar/plugins/modules/qradar_deploy.py b/ansible_collections/ibm/qradar/plugins/modules/qradar_deploy.py new file mode 100644 index 00000000..dc41acd9 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/qradar_deploy.py @@ -0,0 +1,89 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: deploy +short_description: Trigger a qradar configuration deployment +description: + - This module allows for INCREMENTAL or FULL deployments +version_added: "1.0.0" +options: + type: + description: + - Type of deployment + required: false + type: str + choices: + - "INCREMENTAL" + - "FULL" + default: "INCREMENTAL" +notes: + - This module does not support check mode because the QRadar REST API does not offer stateful inspection of configuration deployments + +author: "Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security>" +""" + +EXAMPLES = """ +- name: run an incremental deploy + ibm.qradar.deploy: + type: INCREMENTAL +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, +) + + +def main(): + + argspec = dict( + type=dict( + choices=["INCREMENTAL", "FULL"], + required=False, + default="INCREMENTAL", + ) + ) + + module = AnsibleModule(argument_spec=argspec, supports_check_mode=False) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["state", "type_name", "identifier"], + ) + + qradar_return_data = qradar_request.post_by_path( + "api/staged_config/deploy_status" + ) + + if "message" in qradar_return_data and ( + to_text("No changes to deploy") + in to_text(qradar_return_data["message"]) + ): + module.exit_json( + msg="No changes to deploy", + qradar_return_data=qradar_return_data, + changed=False, + ) + else: + module.exit_json( + msg="Successfully initiated {0} deployment.".format( + module.params["type"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/qradar_log_source_management.py b/ansible_collections/ibm/qradar/plugins/modules/qradar_log_source_management.py new file mode 100644 index 00000000..fa793a59 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/qradar_log_source_management.py @@ -0,0 +1,263 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: log_source_management +short_description: Manage Log Sources in QRadar +description: + - This module allows for addition, deletion, or modification of Log Sources in QRadar +version_added: "1.0.0" +deprecated: + alternative: qradar_log_sources_management + why: Newer and updated modules released with more functionality. + removed_at_date: '2024-09-01' +options: + name: + description: + - Name of Log Source + required: true + type: str + state: + description: + - Add or remove a log source. + required: true + choices: [ "present", "absent" ] + type: str + type_name: + description: + - Type of resource by name + required: false + type: str + type_id: + description: + - Type of resource by id, as defined in QRadar Log Source Types Documentation + required: false + type: int + protocol_type_id: + description: + - Type of protocol by id, as defined in QRadar Log Source Types Documentation + required: false + type: int + identifier: + description: + - Log Source Identifier (Typically IP Address or Hostname of log source) + required: true + type: str + description: + description: + - Description of log source + required: true + type: str + +notes: + - Either C(type) or C(type_id) is required + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + +EXAMPLES = """ +- name: Add a snort log source to IBM QRadar + ibm.qradar.log_source_management: + name: "Snort logs" + type_name: "Snort Open Source IDS" + state: present + description: "Snort IDS remote logs from rsyslog" + identifier: "192.168.1.101" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, + find_dict_in_list, +) + +import json + + +def set_log_source_values(module, qradar_request): + if module.params["type_name"]: + code, query_response = qradar_request.get( + "/api/config/event_sources/log_source_management/log_source_types?filter={0}".format( + quote('name="{0}"'.format(module.params["type_name"])) + ) + ) + log_source_type_found = query_response[0] + if module.params["type_id"]: + code, query_response = qradar_request.get( + "/api/config/event_sources/log_source_management/log_source_types?filter={0}".format( + quote('name="{0}"'.format(module.params["type_name"])) + ) + ) + code, log_source_type_found = query_response[0] + if log_source_type_found: + if not module.params["type_id"]: + module.params["type_id"] = log_source_type_found["id"] + else: + module.fail_json( + msg="Incompatible type provided, please consult QRadar Documentation for Log Source Types" + ) + + if module.params["protocol_type_id"]: + found_dict_in_list, _fdil_index = find_dict_in_list( + log_source_type_found["protocol_types"], + "protocol_id", + module.params["protocol_type_id"], + ) + if not found_dict_in_list: + module.fail_json( + msg="Incompatible protocol_type_id provided, please consult QRadar Documentation for Log Source Types" + ) + else: + # Set it to the default as provided by the QRadar Instance + module.params["protocol_type_id"] = log_source_type_found[ + "protocol_types" + ][0]["protocol_id"] + + module.params["protocol_parameters"] = [ + { + "id": module.params["protocol_type_id"], + "name": "identifier", + "value": module.params["identifier"], + } + ] + + +def main(): + + argspec = dict( + name=dict(required=True, type="str"), + state=dict(choices=["present", "absent"], required=True), + type_name=dict(required=False, type="str"), + type_id=dict(required=False, type="int"), + identifier=dict(required=True, type="str"), + protocol_type_id=dict(required=False, type="int"), + description=dict(required=True, type="str"), + ) + + module = AnsibleModule( + argument_spec=argspec, + required_one_of=[("type_name", "type_id")], + mutually_exclusive=[("type_name", "type_id")], + supports_check_mode=True, + ) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["state", "type_name", "identifier"], + ) + + code, log_source_exists = qradar_request.get( + "/api/config/event_sources/log_source_management/log_sources?filter={0}".format( + quote('name="{0}"'.format(module.params["name"])) + ) + ) + + if log_source_exists: + + if module.params["state"] == "present": + ( + existing_log_source_protocol_identifier, + _elspi_index, + ) = find_dict_in_list( + log_source_exists[0]["protocol_parameters"], + "name", + "identifier", + ) + + set_log_source_values(module, qradar_request) + + comparison_map = [ + existing_log_source_protocol_identifier["value"] + == module.params["identifier"], + log_source_exists[0]["name"] == module.params["name"], + log_source_exists[0]["type_id"] == module.params["type_id"], + to_text(log_source_exists[0]["description"]) + == to_text(module.params["description"]), + ] + + if all(comparison_map): + module.exit_json(changed=False, msg="Nothing to do.") + else: + log_source_exists[0]["protocol_parameters"][ + _elspi_index + ] = module.params["protocol_parameters"][0] + log_source_exists[0]["name"] = module.params["name"] + log_source_exists[0]["type_id"] = module.params["type_id"] + log_source_exists[0]["description"] = module.params[ + "description" + ] + if module.check_mode: + qradar_return_data = { + "EMPTY": "IN CHECK MODE, NO TRANSACTION TOOK PLACE" + } + else: + code, qradar_return_data = qradar_request.create_update( + "api/config/event_sources/log_source_management/log_sources", + data=json.dumps(log_source_exists), + ) + + module.exit_json( + msg="Successfully updated log source: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + + if module.params["state"] == "absent": + if module.check_mode: + qradar_return_data = { + "EMPTY": "IN CHECK MODE, NO TRANSACTION TOOK PLACE" + } + else: + code, qradar_return_data = qradar_request.delete( + "/api/config/event_sources/log_source_management/log_sources/{0}".format( + log_source_exists[0]["id"] + ) + ) + + module.exit_json( + msg="Successfully deleted log source: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + if module.params["state"] == "present": + set_log_source_values(module, qradar_request) + if module.check_mode: + qradar_return_data = { + "EMPTY": "IN CHECK MODE, NO TRANSACTION TOOK PLACE" + } + else: + code, qradar_return_data = qradar_request.create_update( + "api/config/event_sources/log_source_management/log_sources", + data=json.dumps([qradar_request.get_data()]), + ) + + module.exit_json( + msg="Successfully created log source: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + + if module.params["state"] == "absent": + module.exit_json(changed=False, msg="Nothing to do.") + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/qradar_log_sources_management.py b/ansible_collections/ibm/qradar/plugins/modules/qradar_log_sources_management.py new file mode 100644 index 00000000..e5ba46a0 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/qradar_log_sources_management.py @@ -0,0 +1,522 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: qradar_log_sources_management +short_description: Qradar Log Sources Management resource module +description: + - This module allows for addition, deletion, or modification of Log Sources in QRadar +version_added: "2.1.0" +options: + config: + description: A dictionary of Qradar Log Sources options + type: list + elements: dict + suboptions: + name: + description: + - Name of Log Source + type: str + description: + description: + - Description of log source + type: str + type_name: + description: + - Type of resource by name + type: str + type_id: + description: + - The type of the log source. Must correspond to an existing log source type. + type: int + identifier: + description: + - Log Source Identifier (Typically IP Address or Hostname of log source) + type: str + protocol_type_id: + description: + - Type of protocol by id, as defined in QRadar Log Source Types Documentation + type: int + enabled: + description: + - If the log source is enabled, the condition is set to 'true'; otherwise, + the condition is set to 'false'. + type: bool + gateway: + description: + - If the log source is configured as a gateway, the condition is set to 'true'; + otherwise, the condition is set to 'false'. A gateway log source is a stand-alone + protocol configuration. The log source receives no events itself, and serves as a + host for a protocol configuration that retrieves event data to feed other log sources. + It acts as a "gateway" for events from multiple systems to enter the event pipeline. + type: bool + internal: + description: + - If the log source is internal (when the log source type is defined as internal), + the condition is set to 'true'. + type: bool + target_event_collector_id: + description: + - The ID of the event collector where the log source sends its data. + The ID must correspond to an existing event collector. + type: int + coalesce_events: + description: + - If events collected by this log source are coalesced based on common properties, + the condition is set to 'true'. If each individual event is stored, + then the condition is set to 'false'. + type: bool + store_event_payload: + description: + - If the payloads of events that are collected by this log source are stored, + the condition is set to 'true'. If only the normalized event records are stored, + then the condition is set to 'false'. + type: bool + language_id: + description: + - The language of the events that are being processed by this log source. + Must correspond to an existing log source language. Individual log source types + can support only a subset of all available log source languages, + as indicated by the supported_language_ids field of the log source type structure + type: int + group_ids: + description: + - The set of log source group IDs this log source is a member of. + Each ID must correspond to an existing log source group. + type: list + elements: str + requires_deploy: + description: + - Set to 'true' if you need to deploy changes to enable the log source for use; + otherwise, set to 'false' if the log source is already active. + type: bool + status: + description: + - The status of the log source. + type: dict + suboptions: + last_updated: + description: last_updated + type: int + messages: + description: last_updated + type: str + status: + description: last_updated + type: str + average_eps: + description: + - The average events per second (EPS) rate of the log source over the last 60 seconds. + type: int + protocol_parameters: + description: + - The set of protocol parameters + - If not provided module will set the protocol parameters by itself + - Note, parameter will come to use mostly in case when facts are gathered and fired + with some modifications to params or in case of round trip scenarios. + type: list + elements: dict + suboptions: + id: + description: The ID of the protocol type. + type: int + name: + description: The unique name of the protocol type. + type: str + value: + description: The allowed protocol value. + type: str + state: + description: + - The state the configuration should be left in + - The state I(gathered) will get the module API configuration from the device + and transform it into structured data in the format as per the module argspec + and the value is returned in the I(gathered) key within the result. + type: str + choices: + - merged + - replaced + - gathered + - deleted + +author: Ansible Security Automation Team (@justjais) <https://github.com/ansible-security> +""" + +EXAMPLES = """ + +# Using MERGED state +# ------------------- + +- name: Add Snort n Apache log sources to IBM QRadar + ibm.qradar.qradar_log_sources_management: + config: + - name: "Snort logs" + type_name: "Snort Open Source IDS" + description: "Snort IDS remote logs from rsyslog" + identifier: "192.0.2.1" + - name: "Apache HTTP Server logs" + type_name: "Apache HTTP Server" + description: "Apache HTTP Server remote logs from rsyslog" + identifier: "198.51.100.1" + state: merged + +# RUN output: +# ----------- + +# qradar_log_sources_management: +# after: +# - auto_discovered: false +# average_eps: 0 +# coalesce_events: true +# creation_date: 1654727311444 +# credibility: 5 +# description: Snort IDS remote logs from rsyslog +# enabled: true +# gateway: false +# group_ids: +# - 0 +# id: 181 +# internal: false +# language_id: 1 +# last_event_time: 0 +# log_source_extension_id: null +# modified_date: 1654727311444 +# name: Snort logs +# protocol_parameters: +# - id: 1 +# name: incomingPayloadEncoding +# value: UTF-8 +# - id: 0 +# name: identifier +# value: 192.0.2.1 +# protocol_type_id: 0 +# requires_deploy: true +# status: +# last_updated: 0 +# messages: null +# status: NA +# store_event_payload: true +# target_event_collector_id: 7 +# type_id: 2 +# wincollect_external_destination_ids: null +# wincollect_internal_destination_id: null +# - auto_discovered: false +# average_eps: 0 +# coalesce_events: true +# creation_date: 1654727311462 +# credibility: 5 +# description: Apache HTTP Server remote logs from rsyslog +# enabled: true +# gateway: false +# group_ids: +# - 0 +# id: 182 +# internal: false +# language_id: 1 +# last_event_time: 0 +# log_source_extension_id: null +# modified_date: 1654727311462 +# name: Apache HTTP Server logs +# protocol_parameters: +# - id: 1 +# name: incomingPayloadEncoding +# value: UTF-8 +# - id: 0 +# name: identifier +# value: 198.51.100.1 +# protocol_type_id: 0 +# requires_deploy: true +# status: +# last_updated: 0 +# messages: null +# status: NA +# store_event_payload: true +# target_event_collector_id: 7 +# type_id: 10 +# wincollect_external_destination_ids: null +# wincollect_internal_destination_id: null +# before: [] + +# Using REPLACED state +# -------------------- + +- name: Replace existing Log sources to IBM QRadar + ibm.qradar.qradar_log_sources_management: + state: replaced + config: + - name: "Apache HTTP Server logs" + type_name: "Apache HTTP Server" + description: "REPLACED Apache HTTP Server remote logs from rsyslog" + identifier: "192.0.2.1" + +# RUN output: +# ----------- + +# qradar_log_sources_management: +# after: +# - auto_discovered: false +# average_eps: 0 +# coalesce_events: true +# creation_date: 1654727944017 +# credibility: 5 +# description: REPLACED Apache HTTP Server remote logs from rsyslog +# enabled: true +# gateway: false +# group_ids: +# - 0 +# id: 183 +# internal: false +# language_id: 1 +# last_event_time: 0 +# log_source_extension_id: null +# modified_date: 1654727944017 +# name: Apache HTTP Server logs +# protocol_parameters: +# - id: 1 +# name: incomingPayloadEncoding +# value: UTF-8 +# - id: 0 +# name: identifier +# value: 192.0.2.1 +# protocol_type_id: 0 +# requires_deploy: true +# status: +# last_updated: 0 +# messages: null +# status: NA +# store_event_payload: true +# target_event_collector_id: 7 +# type_id: 10 +# wincollect_external_destination_ids: null +# wincollect_internal_destination_id: null +# before: +# - auto_discovered: false +# average_eps: 0 +# coalesce_events: true +# creation_date: 1654727311462 +# credibility: 5 +# description: Apache HTTP Server remote logs from rsyslog +# enabled: true +# gateway: false +# group_ids: +# - 0 +# id: 182 +# internal: false +# language_id: 1 +# last_event_time: 0 +# log_source_extension_id: null +# modified_date: 1654727311462 +# name: Apache HTTP Server logs +# protocol_parameters: +# - name: identifier +# value: 198.51.100.1 +# - name: incomingPayloadEncoding +# value: UTF-8 +# protocol_type_id: 0 +# requires_deploy: true +# status: +# last_updated: 0 +# messages: null +# status: NA +# store_event_payload: true +# target_event_collector_id: 7 +# type_id: 10 +# wincollect_external_destination_ids: null +# wincollect_internal_destination_id: null + +# Using GATHERED state +# -------------------- + +- name: Gather Snort n Apache log source from IBM QRadar + ibm.qradar.qradar_log_sources_management: + config: + - name: "Snort logs" + - name: "Apache HTTP Server logs" + state: gathered + +# RUN output: +# ----------- + +# gathered: +# - auto_discovered: false +# average_eps: 0 +# coalesce_events: true +# creation_date: 1654727311444 +# credibility: 5 +# description: Snort IDS remote logs from rsyslog +# enabled: true +# gateway: false +# group_ids: +# - 0 +# id: 181 +# internal: false +# language_id: 1 +# last_event_time: 0 +# log_source_extension_id: null +# modified_date: 1654728103340 +# name: Snort logs +# protocol_parameters: +# - id: 0 +# name: identifier +# value: 192.0.2.1 +# - id: 1 +# name: incomingPayloadEncoding +# value: UTF-8 +# protocol_type_id: 0 +# requires_deploy: true +# status: +# last_updated: 0 +# messages: null +# status: NA +# store_event_payload: true +# target_event_collector_id: 7 +# type_id: 2 +# wincollect_external_destination_ids: null +# wincollect_internal_destination_id: null +# - auto_discovered: false +# average_eps: 0 +# coalesce_events: true +# creation_date: 1654727944017 +# credibility: 5 +# description: Apache HTTP Server remote logs from rsyslog +# enabled: true +# gateway: false +# group_ids: +# - 0 +# id: 183 +# internal: false +# language_id: 1 +# last_event_time: 0 +# log_source_extension_id: null +# modified_date: 1654728103353 +# name: Apache HTTP Server logs +# protocol_parameters: +# - id: 0 +# name: identifier +# value: 192.0.2.1 +# - id: 1 +# name: incomingPayloadEncoding +# value: UTF-8 +# protocol_type_id: 0 +# requires_deploy: true +# status: +# last_updated: 0 +# messages: null +# status: NA +# store_event_payload: true +# target_event_collector_id: 7 +# type_id: 10 +# wincollect_external_destination_ids: null +# wincollect_internal_destination_id: null + +- name: TO Gather ALL log sources from IBM QRadar + tags: gather_log_all + ibm.qradar.qradar_log_sources_management: + state: gathered + +# Using DELETED state +# ------------------- + +- name: Delete Snort n Apache log source from IBM QRadar + ibm.qradar.qradar_log_sources_management: + config: + - name: "Snort logs" + - name: "Apache HTTP Server logs" + state: deleted + +# RUN output: +# ----------- + +# qradar_log_sources_management: +# after: [] +# before: +# - auto_discovered: false +# average_eps: 0 +# coalesce_events: true +# creation_date: 1654727311444 +# credibility: 5 +# description: Snort IDS remote logs from rsyslog +# enabled: true +# gateway: false +# group_ids: +# - 0 +# id: 181 +# internal: false +# language_id: 1 +# last_event_time: 0 +# log_source_extension_id: null +# modified_date: 1654728103340 +# name: Snort logs +# protocol_parameters: +# - id: 0 +# name: identifier +# value: 192.0.2.1 +# - id: 1 +# name: incomingPayloadEncoding +# value: UTF-8 +# protocol_type_id: 0 +# requires_deploy: true +# status: +# last_updated: 0 +# messages: null +# status: NA +# store_event_payload: true +# target_event_collector_id: 7 +# type_id: 2 +# wincollect_external_destination_ids: null +# wincollect_internal_destination_id: null +# - auto_discovered: false +# average_eps: 0 +# coalesce_events: true +# creation_date: 1654727944017 +# credibility: 5 +# description: Apache HTTP Server remote logs from rsyslog +# enabled: true +# gateway: false +# group_ids: +# - 0 +# id: 183 +# internal: false +# language_id: 1 +# last_event_time: 0 +# log_source_extension_id: null +# modified_date: 1654728103353 +# name: Apache HTTP Server logs +# protocol_parameters: +# - id: 0 +# name: identifier +# value: 192.0.2.1 +# - id: 1 +# name: incomingPayloadEncoding +# value: UTF-8 +# protocol_type_id: 0 +# requires_deploy: true +# status: +# last_updated: 0 +# messages: null +# status: NA +# store_event_payload: true +# target_event_collector_id: 7 +# type_id: 10 +# wincollect_external_destination_ids: null +# wincollect_internal_destination_id: null +""" + +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: The configuration returned will always be in the same format of the parameters above. +after: + description: The configuration as structured data after module completion. + returned: when changed + type: list + sample: The configuration returned will always be in the same format of the parameters above. +""" diff --git a/ansible_collections/ibm/qradar/plugins/modules/qradar_offense_action.py b/ansible_collections/ibm/qradar/plugins/modules/qradar_offense_action.py new file mode 100644 index 00000000..784f1e7b --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/qradar_offense_action.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: offense_action +short_description: Take action on a QRadar Offense +description: + - This module allows to assign, protect, follow up, set status, and assign closing reason to QRadar Offenses +version_added: "1.0.0" +options: + id: + description: + - ID of Offense + required: true + type: int + status: + description: + - One of "open", "hidden" or "closed". (Either all lower case or all caps) + required: false + choices: [ "open", "OPEN", "hidden", "HIDDEN", "closed", "CLOSED" ] + type: str + assigned_to: + description: + - Assign to an user, the QRadar username should be provided + required: false + type: str + closing_reason: + description: + - Assign a predefined closing reason here, by name. + required: false + type: str + closing_reason_id: + description: + - Assign a predefined closing reason here, by id. + required: false + type: int + follow_up: + description: + - Set or unset the flag to follow up on a QRadar Offense + required: false + type: bool + protected: + description: + - Set or unset the flag to protect a QRadar Offense + required: false + type: bool + +notes: + - Requires one of C(name) or C(id) be provided + - Only one of C(closing_reason) or C(closing_reason_id) can be provided + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + +""" +# FIXME - WOULD LIKE TO QUERY BY NAME BUT HOW TO ACCOMPLISH THAT IS NON-OBVIOUS +# name: +# description: +# - Name of Offense +# required: true +# type: str +""" + +EXAMPLES = """ +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, + set_offense_values, +) + + +def main(): + + argspec = dict( + # name=dict(required=False, type='str'), + # id=dict(required=False, type='str'), + id=dict(required=True, type="int"), + assigned_to=dict(required=False, type="str"), + closing_reason=dict(required=False, type="str"), + closing_reason_id=dict(required=False, type="int"), + follow_up=dict(required=False, type="bool"), + protected=dict(required=False, type="bool"), + status=dict( + required=False, + choices=["open", "OPEN", "hidden", "HIDDEN", "closed", "CLOSED"], + type="str", + ), + ) + + module = AnsibleModule( + argument_spec=argspec, + # required_one_of=[ + # ('name', 'id',), + # ], + mutually_exclusive=[("closing_reason", "closing_reason_id")], + supports_check_mode=True, + ) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["name", "id", "assigned_to", "closing_reason"], + ) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME + # found_offense = qradar_request.get('/api/siem/offenses?filter={0}'.format(module.params['name'])) + + code, found_offense = qradar_request.get( + "/api/siem/offenses/{0}".format(module.params["id"]) + ) + + if found_offense: + set_offense_values(module, qradar_request) + + post_strs = [] + + if module.params["status"] and ( + to_text(found_offense["status"]) + != to_text(module.params["status"]) + ): + post_strs.append( + "status={0}".format(to_text(module.params["status"])) + ) + + if module.params["assigned_to"] and ( + to_text(found_offense["assigned_to"]) + != to_text(module.params["assigned_to"]) + ): + post_strs.append( + "assigned_to={0}".format(module.params["assigned_to"]) + ) + + if module.params["closing_reason_id"] and ( + found_offense["closing_reason_id"] + != module.params["closing_reason_id"] + ): + post_strs.append( + "closing_reason_id={0}".format( + module.params["closing_reason_id"] + ) + ) + + if module.params["follow_up"] and ( + found_offense["follow_up"] != module.params["follow_up"] + ): + post_strs.append( + "follow_up={0}".format(module.params["follow_up"]) + ) + + if module.params["protected"] and ( + found_offense["protected"] != module.params["protected"] + ): + post_strs.append( + "protected={0}".format(module.params["protected"]) + ) + + if post_strs: + if module.check_mode: + module.exit_json( + msg="A change would have been made but was not because of Check Mode.", + changed=True, + ) + + qradar_return_data = qradar_request.post_by_path( + "api/siem/offenses/{0}?{1}".format( + module.params["id"], "&".join(post_strs) + ) + ) + # FIXME - handle the scenario in which we can search by name and this isn't a required param anymore + module.exit_json( + msg="Successfully updated Offense ID: {0}".format( + module.params["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + module.exit_json( + msg="No changes necessary. Nothing to do.", changed=False + ) + else: + # FIXME - handle the scenario in which we can search by name and this isn't a required param anymore + module.fail_json( + msg="Unable to find Offense ID: {0}".format(module.params["id"]) + ) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/qradar_offense_info.py b/ansible_collections/ibm/qradar/plugins/modules/qradar_offense_info.py new file mode 100644 index 00000000..1ead8a1a --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/qradar_offense_info.py @@ -0,0 +1,216 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: offense_info +short_description: Obtain information about one or many QRadar Offenses, with filter options +description: + - This module allows to obtain information about one or many QRadar Offenses, with filter options +version_added: "1.0.0" +options: + id: + description: + - Obtain only information of the Offense with provided ID + required: false + type: int + name: + description: + - Obtain only information of the Offense that matches the provided name + required: false + type: str + status: + description: + - Obtain only information of Offenses of a certain status + required: false + choices: [ "open", "OPEN", "hidden", "HIDDEN", "closed", "CLOSED" ] + default: "open" + type: str + assigned_to: + description: + - Obtain only information of Offenses assigned to a certain user + required: false + type: str + closing_reason: + description: + - Obtain only information of Offenses that were closed by a specific closing reason + required: false + type: str + closing_reason_id: + description: + - Obtain only information of Offenses that were closed by a specific closing reason ID + required: false + type: int + follow_up: + description: + - Obtain only information of Offenses that are marked with the follow up flag + required: false + type: bool + protected: + description: + - Obtain only information of Offenses that are protected + required: false + type: bool +notes: + - You may provide many filters and they will all be applied, except for C(id) + as that will return only + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + + +# FIXME - provide correct example here +RETURN = """ +offenses: + description: Information + returned: always + type: list + elements: dict + contains: + qradar_offenses: + description: IBM QRadar Offenses found based on provided filters + returned: always + type: complex + contains: + source: + description: Init system of the service. One of C(systemd), C(sysv), C(upstart). + returned: always + type: str + sample: sysv + state: + description: State of the service. Either C(running), C(stopped), or C(unknown). + returned: always + type: str + sample: running + status: + description: State of the service. Either C(enabled), C(disabled), or C(unknown). + returned: systemd systems or RedHat/SUSE flavored sysvinit/upstart + type: str + sample: enabled + name: + description: Name of the service. + returned: always + type: str + sample: arp-ethers.service +""" + + +EXAMPLES = """ +- name: Get list of all currently OPEN IBM QRadar Offenses + ibm.qradar.offense_info: + status: OPEN + register: offense_list + +- name: display offense information for debug purposes + debug: + var: offense_list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, + find_dict_in_list, + set_offense_values, +) + + +def main(): + + argspec = dict( + id=dict(required=False, type="int"), + name=dict(required=False, type="str"), + assigned_to=dict(required=False, type="str"), + closing_reason=dict(required=False, type="str"), + closing_reason_id=dict(required=False, type="int"), + follow_up=dict(required=False, type="bool", default=None), + protected=dict(required=False, type="bool", default=None), + status=dict( + required=False, + choices=["open", "OPEN", "hidden", "HIDDEN", "closed", "CLOSED"], + default="open", + type="str", + ), + ) + + module = AnsibleModule( + argument_spec=argspec, + mutually_exclusive=[("closing_reason", "closing_reason_id")], + supports_check_mode=True, + ) + + qradar_request = QRadarRequest(module) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME NATIVELY VIA REST API (DOESN'T EXIST YET) + # found_offense = qradar_request.get('/api/siem/offenses?filter={0}'.format(module.params['name'])) + + set_offense_values(module, qradar_request) + + if module.params["id"]: + code, offenses = qradar_request.get( + "/api/siem/offenses/{0}".format(module.params["id"]) + ) + + else: + query_strs = [] + + if module.params["status"]: + query_strs.append( + quote("status={0}".format(to_text(module.params["status"]))) + ) + + if module.params["assigned_to"]: + query_strs.append( + quote("assigned_to={0}".format(module.params["assigned_to"])) + ) + + if module.params["closing_reason_id"]: + query_strs.append( + quote( + "closing_reason_id={0}".format( + module.params["closing_reason_id"] + ) + ) + ) + + if module.params["follow_up"] is not None: + query_strs.append( + quote("follow_up={0}".format(module.params["follow_up"])) + ) + + if module.params["protected"] is not None: + query_strs.append( + quote("protected={0}".format(module.params["protected"])) + ) + + if query_strs: + code, offenses = qradar_request.get( + "/api/siem/offenses?filter={0}".format("&".join(query_strs)) + ) + else: + code, offenses = qradar_request.get("/api/siem/offenses") + + if module.params["name"]: + named_offense = find_dict_in_list( + offenses, "description", module.params["name"] + ) + if named_offense: + offenses = named_offense + else: + offenses = [] + + module.exit_json(offenses=offenses, changed=False) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/qradar_offense_note.py b/ansible_collections/ibm/qradar/plugins/modules/qradar_offense_note.py new file mode 100644 index 00000000..53b6ec95 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/qradar_offense_note.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: offense_note +short_description: Create or update a QRadar Offense Note +description: + - This module allows to create a QRadar Offense note +version_added: "1.0.0" +options: + id: + description: + - Offense ID to operate on + required: true + type: int + note_text: + description: The note's text contents + required: true + type: str + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + +""" +# FIXME - WOULD LIKE TO QUERY BY NAME BUT HOW TO ACCOMPLISH THAT IS NON-OBVIOUS +# offense_name: +# description: +# - Name of Offense +# required: true +# type: str + +# FIXME - WOULD LIKE TO MANAGE STATE +# state: +# description: Define state of the note: present or absent +# required: false +# choices: ["present", "absent"] +# default: "present" +""" + +EXAMPLES = """ +- name: Add a note to QRadar Offense ID 1 + ibm.qradar.offense_note: + id: 1 + note_text: This an example note entry that should be made on offense id 1 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, +) + + +def set_offense_values(module, qradar_request): + if module.params["closing_reason"]: + code, found_closing_reason = qradar_request.get( + "/api/siem/offense_closing_reasons?filter={0}".format( + quote('text="{0}"'.format(module.params["closing_reason"])) + ) + ) + if found_closing_reason: + module.params["closing_reason_id"] = found_closing_reason[0]["id"] + else: + module.fail_json( + "Unable to find closing_reason text: {0}".format( + module.params["closing_reason"] + ) + ) + + if module.params["status"]: + module.params["status"] = module.params["status"].upper() + + +def main(): + + argspec = dict( + # state=dict(required=False, choices=["present", "absent"], type='str', default="present"), + id=dict(required=True, type="int"), + note_text=dict(required=True, type="str"), + ) + + module = AnsibleModule(argument_spec=argspec, supports_check_mode=True) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["state", "id"], + ) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME + # found_offense = qradar_request.get('/api/siem/offenses?filter={0}'.format(module.params['name'])) + # FIXME - once this is sorted, add it to module_utils + + code, found_notes = qradar_request.get( + "/api/siem/offenses/{0}/notes?filter={1}".format( + module.params["id"], + quote('note_text="{0}"'.format(module.params["note_text"])), + ) + ) + + # if module.params['state'] == 'present': + + if found_notes: + # The note we want exists either by ID or by text name, verify + + note = found_notes[0] + if note["note_text"] == module.params["note_text"]: + module.exit_json( + msg="No changes necessary. Nothing to do.", changed=False + ) + else: + if module.check_mode: + module.exit_json( + msg="A change would have occured but did not because Check Mode", + changed=True, + ) + + qradar_return_data = qradar_request.post_by_path( + "api/siem/offenses/{0}/notes?note_text={1}".format( + module.params["id"], + quote("{0}".format(module.params["note_text"])), + ), + data=False, + ) + module.exit_json( + msg="Successfully created Offense Note ID: {0}".format( + qradar_return_data["id"] + ), + qradar_return_data=qradar_return_data, + changed=False, + ) + + else: + if module.check_mode: + module.exit_json( + msg="A change would have occured but did not because Check Mode", + changed=True, + ) + + qradar_return_data = qradar_request.post_by_path( + "api/siem/offenses/{0}/notes?note_text={1}".format( + module.params["id"], + quote("{0}".format(module.params["note_text"])), + ), + data=False, + ) + module.exit_json( + msg="Successfully created Offense Note ID: {0}".format( + qradar_return_data["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + + module.exit_json(msg="No changes necessary. Nothing to do.", changed=False) + + # FIXME FIXME FIXME - can we actually delete these via the REST API? + # if module.params['state'] == 'absent': + # if not found_notes: + # module.exit_json(msg="No changes necessary. Nothing to do.", changed=False) + # else: + # if module.check_mode: + # module.exit_json(msg="A change would have occured but did not because Check Mode", changed=True) + # # FIXME: fix the POST here to actually delete + # qradar_return_data = qradar_request.post_by_path( + # 'api/siem/offenses/{0}/notes?note_text={1}'.format( + # module.params['id'], + # quote("{0}".format(module.params['note_text'])), + # ), + # data=False + # ) + # module.exit_json( + # msg="Successfully created Offense Note ID: {0}".format(qradar_return_data['id']), + # qradar_return_data=qradar_return_data, + # changed=True + # ) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/qradar_rule.py b/ansible_collections/ibm/qradar/plugins/modules/qradar_rule.py new file mode 100644 index 00000000..f99bab13 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/qradar_rule.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: rule +short_description: Manage state of QRadar Rules, with filter options +description: + - Manage state of QRadar Rules, with filter options +version_added: "1.0.0" +deprecated: + alternative: qradar_analytics_rules + why: Newer and updated modules released with more functionality. + removed_at_date: '2024-09-01' +options: + id: + description: + - Manage state of a QRadar Rule by ID + required: false + type: int + name: + description: + - Manage state of a QRadar Rule by name + required: false + type: str + state: + description: + - Manage state of a QRadar Rule + required: True + choices: [ "enabled", "disabled", "absent" ] + type: str + owner: + description: + - Manage ownership of a QRadar Rule + required: false + type: str + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + + +# FIXME - provide correct example here +RETURN = """ +""" + +EXAMPLES = """ +- name: Enable Rule 'Ansible Example DDoS Rule' + qradar_rule: + name: 'Ansible Example DDOS Rule' + state: enabled +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, +) +import json + + +def main(): + + argspec = dict( + id=dict(required=False, type="int"), + name=dict(required=False, type="str"), + state=dict( + required=True, + choices=["enabled", "disabled", "absent"], + type="str", + ), + owner=dict(required=False, type="str"), + ) + + module = AnsibleModule( + argument_spec=argspec, + supports_check_mode=True, + required_one_of=[("name", "id")], + mutually_exclusive=[("name", "id")], + ) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["id", "name", "state", "owner"], + ) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME NATIVELY VIA REST API (DOESN'T EXIST YET) + # found_offense = qradar_request.get('/api/analytics/rules?filter={0}'.format(module.params['name'])) + module.params["rule"] = {} + + if module.params["id"]: + code, module.params["rule"] = qradar_request.get( + "/api/analytics/rules/{0}".format(module.params["id"]) + ) + + elif module.params["name"]: + code, rules = qradar_request.get( + "/api/analytics/rules?filter={0}".format( + quote('"{0}"'.format(module.params["name"])) + ) + ) + if rules: + module.params["rule"] = rules[0] + module.params["id"] = rules[0]["id"] + + if module.params["state"] == "enabled": + if module.params["rule"]: + if module.params["rule"]["enabled"] is True: + # Already enabled + if module.params["id"]: + module.exit_json( + msg="No change needed for rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data={}, + changed=False, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully enabled rule named: {0}".format( + module.params["name"] + ), + qradar_return_data={}, + changed=False, + ) + else: + # Not enabled, enable It + module.params["rule"]["enabled"] = True + + qradar_return_data = qradar_request.post_by_path( + "api/analytics/rules/{0}".format( + module.params["rule"]["id"] + ), + data=json.dumps(module.params["rule"]), + ) + if module.params["id"]: + module.exit_json( + msg="Successfully enabled rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully enabled rule named: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + if module.params["id"]: + module.fail_json( + msg="Unable to find rule ID: {0}".format( + module.params["id"] + ) + ) + if module.params["name"]: + module.fail_json( + msg='Unable to find rule named: "{0}"'.format( + module.params["name"] + ) + ) + + elif module.params["state"] == "disabled": + if module.params["rule"]: + if module.params["rule"]["enabled"] is False: + # Already disabled + if module.params["id"]: + module.exit_json( + msg="No change needed for rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data={}, + changed=False, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully enabled rule named: {0}".format( + module.params["name"] + ), + qradar_return_data={}, + changed=False, + ) + else: + # Not disabled, disable It + module.params["rule"]["enabled"] = False + + qradar_return_data = qradar_request.post_by_path( + "api/analytics/rules/{0}".format( + module.params["rule"]["id"] + ), + data=json.dumps(module.params["rule"]), + ) + if module.params["id"]: + module.exit_json( + msg="Successfully disabled rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully disabled rule named: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + if module.params["id"]: + module.fail_json( + msg="Unable to find rule ID: {0}".format( + module.params["id"] + ) + ) + if module.params["name"]: + module.fail_json( + msg='Unable to find rule named: "{0}"'.format( + module.params["name"] + ) + ) + + elif module.params["state"] == "absent": + if module.params["rule"]: + code, qradar_return_data = qradar_request.delete( + "/api/analytics/rules/{0}".format(module.params["rule"]["id"]) + ) + if module.params["id"]: + module.exit_json( + msg="Successfully deleted rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully deleted rule named: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + module.exit_json(msg="Nothing to do, rule not found.") + + module.exit_json(rules=rules, changed=False) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/qradar_rule_info.py b/ansible_collections/ibm/qradar/plugins/modules/qradar_rule_info.py new file mode 100644 index 00000000..d5c92453 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/qradar_rule_info.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: rule_info +short_description: Obtain information about one or many QRadar Rules, with filter options +description: + - This module obtains information about one or many QRadar Rules, with filter options +version_added: "1.0.0" +deprecated: + alternative: qradar_analytics_rules + why: Newer and updated modules released with more functionality. + removed_at_date: '2024-09-01' +options: + id: + description: + - Obtain only information of the Rule with provided ID + required: false + type: int + name: + description: + - Obtain only information of the Rule that matches the provided name + required: false + type: str + type: + description: + - Obtain only information for the Rules of a certain type + required: false + choices: [ "EVENT", "FLOW", "COMMON", "USER"] + type: str + owner: + description: + - Obtain only information of Rules owned by a certain user + required: false + type: str + origin: + description: + - Obtain only information of Rules that are of a certain origin + required: false + choices: ["SYSTEM", "OVERRIDE", "USER"] + type: str +notes: + - You may provide many filters and they will all be applied, except for C(id) + as that will return only the Rule identified by the unique ID provided. + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security>" +""" + + +# FIXME - provide correct example here +RETURN = """ +""" + +EXAMPLES = """ +- name: Get information about the Rule named "Custom Company DDoS Rule" + ibm.qradar.rule_info: + name: "Custom Company DDoS Rule" + register: custom_ddos_rule_info + +- name: debugging output of the custom_ddos_rule_info registered variable + debug: + var: custom_ddos_rule_info +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, +) + + +def main(): + + argspec = dict( + id=dict(required=False, type="int"), + name=dict(required=False, type="str"), + owner=dict(required=False, type="str"), + type=dict( + required=False, + choices=["EVENT", "FLOW", "COMMON", "USER"], + type="str", + ), + origin=dict( + required=False, choices=["SYSTEM", "OVERRIDE", "USER"], type="str" + ), + ) + + module = AnsibleModule(argument_spec=argspec, supports_check_mode=True) + + qradar_request = QRadarRequest(module) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME NATIVELY VIA REST API (DOESN'T EXIST YET) + # found_offense = qradar_request.get('/api/analytics/rules?filter={0}'.format(module.params['name'])) + + if module.params["id"]: + code, rules = qradar_request.get( + "/api/analytics/rules/{0}".format(module.params["id"]) + ) + + else: + query_strs = [] + + if module.params["name"]: + query_strs.append( + quote('name="{0}"'.format(to_text(module.params["name"]))) + ) + + if module.params["owner"]: + query_strs.append( + quote("owner={0}".format(module.params["owner"])) + ) + + if module.params["type"]: + query_strs.append(quote("type={0}".format(module.params["type"]))) + + if module.params["origin"]: + query_strs.append( + quote("origin={0}".format(module.params["origin"])) + ) + + if query_strs: + code, rules = qradar_request.get( + "/api/analytics/rules?filter={0}".format("&".join(query_strs)) + ) + else: + code, rules = qradar_request.get("/api/analytics/rules") + + module.exit_json(rules=rules, changed=False) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/rule.py b/ansible_collections/ibm/qradar/plugins/modules/rule.py new file mode 100644 index 00000000..f99bab13 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/rule.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: rule +short_description: Manage state of QRadar Rules, with filter options +description: + - Manage state of QRadar Rules, with filter options +version_added: "1.0.0" +deprecated: + alternative: qradar_analytics_rules + why: Newer and updated modules released with more functionality. + removed_at_date: '2024-09-01' +options: + id: + description: + - Manage state of a QRadar Rule by ID + required: false + type: int + name: + description: + - Manage state of a QRadar Rule by name + required: false + type: str + state: + description: + - Manage state of a QRadar Rule + required: True + choices: [ "enabled", "disabled", "absent" ] + type: str + owner: + description: + - Manage ownership of a QRadar Rule + required: false + type: str + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security> +""" + + +# FIXME - provide correct example here +RETURN = """ +""" + +EXAMPLES = """ +- name: Enable Rule 'Ansible Example DDoS Rule' + qradar_rule: + name: 'Ansible Example DDOS Rule' + state: enabled +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, +) +import json + + +def main(): + + argspec = dict( + id=dict(required=False, type="int"), + name=dict(required=False, type="str"), + state=dict( + required=True, + choices=["enabled", "disabled", "absent"], + type="str", + ), + owner=dict(required=False, type="str"), + ) + + module = AnsibleModule( + argument_spec=argspec, + supports_check_mode=True, + required_one_of=[("name", "id")], + mutually_exclusive=[("name", "id")], + ) + + qradar_request = QRadarRequest( + module, + not_rest_data_keys=["id", "name", "state", "owner"], + ) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME NATIVELY VIA REST API (DOESN'T EXIST YET) + # found_offense = qradar_request.get('/api/analytics/rules?filter={0}'.format(module.params['name'])) + module.params["rule"] = {} + + if module.params["id"]: + code, module.params["rule"] = qradar_request.get( + "/api/analytics/rules/{0}".format(module.params["id"]) + ) + + elif module.params["name"]: + code, rules = qradar_request.get( + "/api/analytics/rules?filter={0}".format( + quote('"{0}"'.format(module.params["name"])) + ) + ) + if rules: + module.params["rule"] = rules[0] + module.params["id"] = rules[0]["id"] + + if module.params["state"] == "enabled": + if module.params["rule"]: + if module.params["rule"]["enabled"] is True: + # Already enabled + if module.params["id"]: + module.exit_json( + msg="No change needed for rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data={}, + changed=False, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully enabled rule named: {0}".format( + module.params["name"] + ), + qradar_return_data={}, + changed=False, + ) + else: + # Not enabled, enable It + module.params["rule"]["enabled"] = True + + qradar_return_data = qradar_request.post_by_path( + "api/analytics/rules/{0}".format( + module.params["rule"]["id"] + ), + data=json.dumps(module.params["rule"]), + ) + if module.params["id"]: + module.exit_json( + msg="Successfully enabled rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully enabled rule named: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + if module.params["id"]: + module.fail_json( + msg="Unable to find rule ID: {0}".format( + module.params["id"] + ) + ) + if module.params["name"]: + module.fail_json( + msg='Unable to find rule named: "{0}"'.format( + module.params["name"] + ) + ) + + elif module.params["state"] == "disabled": + if module.params["rule"]: + if module.params["rule"]["enabled"] is False: + # Already disabled + if module.params["id"]: + module.exit_json( + msg="No change needed for rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data={}, + changed=False, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully enabled rule named: {0}".format( + module.params["name"] + ), + qradar_return_data={}, + changed=False, + ) + else: + # Not disabled, disable It + module.params["rule"]["enabled"] = False + + qradar_return_data = qradar_request.post_by_path( + "api/analytics/rules/{0}".format( + module.params["rule"]["id"] + ), + data=json.dumps(module.params["rule"]), + ) + if module.params["id"]: + module.exit_json( + msg="Successfully disabled rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully disabled rule named: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + if module.params["id"]: + module.fail_json( + msg="Unable to find rule ID: {0}".format( + module.params["id"] + ) + ) + if module.params["name"]: + module.fail_json( + msg='Unable to find rule named: "{0}"'.format( + module.params["name"] + ) + ) + + elif module.params["state"] == "absent": + if module.params["rule"]: + code, qradar_return_data = qradar_request.delete( + "/api/analytics/rules/{0}".format(module.params["rule"]["id"]) + ) + if module.params["id"]: + module.exit_json( + msg="Successfully deleted rule ID: {0}".format( + module.params["id"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + if module.params["name"]: + module.exit_json( + msg="Successfully deleted rule named: {0}".format( + module.params["name"] + ), + qradar_return_data=qradar_return_data, + changed=True, + ) + else: + module.exit_json(msg="Nothing to do, rule not found.") + + module.exit_json(rules=rules, changed=False) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/ibm/qradar/plugins/modules/rule_info.py b/ansible_collections/ibm/qradar/plugins/modules/rule_info.py new file mode 100644 index 00000000..d5c92453 --- /dev/null +++ b/ansible_collections/ibm/qradar/plugins/modules/rule_info.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Adam Miller (admiller@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: rule_info +short_description: Obtain information about one or many QRadar Rules, with filter options +description: + - This module obtains information about one or many QRadar Rules, with filter options +version_added: "1.0.0" +deprecated: + alternative: qradar_analytics_rules + why: Newer and updated modules released with more functionality. + removed_at_date: '2024-09-01' +options: + id: + description: + - Obtain only information of the Rule with provided ID + required: false + type: int + name: + description: + - Obtain only information of the Rule that matches the provided name + required: false + type: str + type: + description: + - Obtain only information for the Rules of a certain type + required: false + choices: [ "EVENT", "FLOW", "COMMON", "USER"] + type: str + owner: + description: + - Obtain only information of Rules owned by a certain user + required: false + type: str + origin: + description: + - Obtain only information of Rules that are of a certain origin + required: false + choices: ["SYSTEM", "OVERRIDE", "USER"] + type: str +notes: + - You may provide many filters and they will all be applied, except for C(id) + as that will return only the Rule identified by the unique ID provided. + +author: Ansible Security Automation Team (@maxamillion) <https://github.com/ansible-security>" +""" + + +# FIXME - provide correct example here +RETURN = """ +""" + +EXAMPLES = """ +- name: Get information about the Rule named "Custom Company DDoS Rule" + ibm.qradar.rule_info: + name: "Custom Company DDoS Rule" + register: custom_ddos_rule_info + +- name: debugging output of the custom_ddos_rule_info registered variable + debug: + var: custom_ddos_rule_info +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible_collections.ibm.qradar.plugins.module_utils.qradar import ( + QRadarRequest, +) + + +def main(): + + argspec = dict( + id=dict(required=False, type="int"), + name=dict(required=False, type="str"), + owner=dict(required=False, type="str"), + type=dict( + required=False, + choices=["EVENT", "FLOW", "COMMON", "USER"], + type="str", + ), + origin=dict( + required=False, choices=["SYSTEM", "OVERRIDE", "USER"], type="str" + ), + ) + + module = AnsibleModule(argument_spec=argspec, supports_check_mode=True) + + qradar_request = QRadarRequest(module) + + # if module.params['name']: + # # FIXME - QUERY HERE BY NAME NATIVELY VIA REST API (DOESN'T EXIST YET) + # found_offense = qradar_request.get('/api/analytics/rules?filter={0}'.format(module.params['name'])) + + if module.params["id"]: + code, rules = qradar_request.get( + "/api/analytics/rules/{0}".format(module.params["id"]) + ) + + else: + query_strs = [] + + if module.params["name"]: + query_strs.append( + quote('name="{0}"'.format(to_text(module.params["name"]))) + ) + + if module.params["owner"]: + query_strs.append( + quote("owner={0}".format(module.params["owner"])) + ) + + if module.params["type"]: + query_strs.append(quote("type={0}".format(module.params["type"]))) + + if module.params["origin"]: + query_strs.append( + quote("origin={0}".format(module.params["origin"])) + ) + + if query_strs: + code, rules = qradar_request.get( + "/api/analytics/rules?filter={0}".format("&".join(query_strs)) + ) + else: + code, rules = qradar_request.get("/api/analytics/rules") + + module.exit_json(rules=rules, changed=False) + + +if __name__ == "__main__": + main() |