summaryrefslogtreecommitdiffstats
path: root/ansible_collections/ibm/qradar/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:03:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:03:42 +0000
commit66cec45960ce1d9c794e9399de15c138acb18aed (patch)
tree59cd19d69e9d56b7989b080da7c20ef1a3fe2a5a /ansible_collections/ibm/qradar/plugins
parentInitial commit. (diff)
downloadansible-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')
-rw-r--r--ansible_collections/ibm/qradar/plugins/action/__init__.py0
-rw-r--r--ansible_collections/ibm/qradar/plugins/action/qradar_analytics_rules.py226
-rw-r--r--ansible_collections/ibm/qradar/plugins/action/qradar_log_sources_management.py306
-rw-r--r--ansible_collections/ibm/qradar/plugins/httpapi/qradar.py76
-rw-r--r--ansible_collections/ibm/qradar/plugins/module_utils/qradar.py254
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/deploy.py89
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/log_source_management.py263
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/offense_action.py201
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/offense_info.py216
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/offense_note.py189
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/qradar_analytics_rules.py279
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/qradar_deploy.py89
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/qradar_log_source_management.py263
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/qradar_log_sources_management.py522
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/qradar_offense_action.py201
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/qradar_offense_info.py216
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/qradar_offense_note.py189
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/qradar_rule.py262
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/qradar_rule_info.py143
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/rule.py262
-rw-r--r--ansible_collections/ibm/qradar/plugins/modules/rule_info.py143
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()