diff --git a/Packs/Code42/Integrations/Code42/CHANGELOG.md b/Packs/Code42/Integrations/Code42/CHANGELOG.md index 244b5e76f237..dd10bb0a433f 100644 --- a/Packs/Code42/Integrations/Code42/CHANGELOG.md +++ b/Packs/Code42/Integrations/Code42/CHANGELOG.md @@ -1,5 +1,7 @@ ## [Unreleased] Added new commands: + - **Code42GetDepartingEmployeeAlerts** automation script which gets departing employees with alerts. + - **code42-alert-search** that searches alerts by username. - **code42-departingemployee-get-all** that gets all the employees on the Departing Employee List. - **code42-highriskemployee-add** that takes a username and adds the employee to the High Risk Employee List. - **code42-highriskemployee-remove** that takes a username and remove the employee from the High Risk Employee List. diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 8d83633fe331..de3f2f00e056 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -20,7 +20,7 @@ FileCategory, ) from py42.sdk.queries.alerts.alert_query import AlertQuery -from py42.sdk.queries.alerts.filters import DateObserved, Severity, AlertState +from py42.sdk.queries.alerts.filters import DateObserved, Severity, AlertState, Actor as AlertActor # Disable insecure warnings requests.packages.urllib3.disable_warnings() @@ -216,6 +216,10 @@ def fetch_alerts(self, start_time, event_severity_filter): res = self._sdk.alerts.search(query) return res["alerts"] + def search_alerts(self, username): + query = AlertQuery(AlertActor.eq(username)) + return self._sdk.alerts.search(query)["alerts"] + def get_alert_details(self, alert_id): res = self._sdk.alerts.get_details(alert_id)["alerts"] if not res: @@ -509,6 +513,27 @@ def alert_resolve_command(client, args): return_error(create_command_error_message(demisto.command(), e)) +def alert_search_command(client, args): + username = args["username"] + try: + alerts = client.search_alerts(username) + alert_context = [] + for alert in alerts: + alert_context.append(map_to_code42_alert_context(alert)) + readable_outputs = tableToMarkdown( + "Code42 Security Alert Search", + alert_context, + headers=SECURITY_ALERT_HEADERS, + ) + return ( + readable_outputs, + {"Code42.SecurityAlert": alert_context}, + alerts + ) + + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) + @logger def departingemployee_add_command(client, args): departing_date = args.get("departuredate") @@ -815,6 +840,7 @@ def main(): commands = { "code42-alert-get": alert_get_command, "code42-alert-resolve": alert_resolve_command, + "code42-alert-search": alert_search_command, "code42-securitydata-search": securitydata_search_command, "code42-departingemployee-add": departingemployee_add_command, "code42-departingemployee-remove": departingemployee_remove_command, diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index e914c2b7feaa..a5b742574476 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -240,7 +240,7 @@ script: - contextPath: Code42.SecurityAlert.Severity description: The severity of the alert. type: string - description: Retrieve alert details by alert ID + description: Retrieve alert details by alert ID. - name: code42-alert-resolve arguments: - name: id @@ -252,6 +252,37 @@ script: description: The alert ID of the resolved alert. type: string description: Resolves a Code42 Security alert. + - name: code42-alert-search + arguments: + - name: username + required: true + description: The username for the user to search alerts for. + outputs: + - contextPath: Code42.SecurityAlert.Username + description: The username associated with the alert. + type: string + - contextPath: Code42.SecurityAlert.Occurred + description: The timestamp when the alert occurred. + type: date + - contextPath: Code42.SecurityAlert.Description + description: The description of the alert. + type: string + - contextPath: Code42.SecurityAlert.ID + description: The alert ID. + type: string + - contextPath: Code42.SecurityAlert.Name + description: The alert rule name that generated the alert. + type: string + - contextPath: Code42.SecurityAlert.State + description: The alert state. + type: string + - contextPath: Code42.SecurityAlert.Type + description: The alert type. + type: string + - contextPath: Code42.SecurityAlert.Severity + description: The severity of the alert. + type: string + description: Search alerts by username. - name: code42-departingemployee-add arguments: - name: username @@ -362,6 +393,7 @@ script: type: string - contextPath: Code42.HighRiskEmployee.RiskTags description: Risk tags to associate with the High Risk Employee. + description: Add the given risk tags to the user with the given username. - name: code42-highriskemployee-remove-risk-tags arguments: - name: username @@ -379,6 +411,7 @@ script: type: string - contextPath: Code42.HighRiskEmployee.RiskTags description: Risk tags to disassociate from the High Risk Employee. + description: Remove the given risk tags from the user with the given username. dockerimage: demisto/py42:1.0.0.9242 isfetch: true runonce: false diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index e2d7dba7ce58..328a11c3f257 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -12,6 +12,7 @@ map_to_file_context, alert_get_command, alert_resolve_command, + alert_search_command, departingemployee_add_command, departingemployee_remove_command, departingemployee_get_all_command, @@ -994,6 +995,12 @@ def test_alert_resolve_command(code42_alerts_mock): assert res["id"] == "36fb8ca5-0533-4d25-9763-e09d35d60610" +def test_alert_search_command(code42_alerts_mock): + client = create_client(code42_alerts_mock) + _, _, res = alert_search_command(client, {"username": "user1@example.com"}) + assert res == json.loads(MOCK_ALERTS_RESPONSE)["alerts"] + + def test_departingemployee_add_command(code42_sdk_mock): client = create_client(code42_sdk_mock) _, _, res = departingemployee_add_command( diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 1bf98b61977e..a04731a12049 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -81,7 +81,7 @@ script: from py42.sdk.queries.alerts.alert_query import AlertQuery - from py42.sdk.queries.alerts.filters import DateObserved, Severity, AlertState + from py42.sdk.queries.alerts.filters import DateObserved, Severity, AlertState, Actor as AlertActor # Disable insecure warnings @@ -287,6 +287,10 @@ script: res = self._sdk.alerts.search(query) return res["alerts"] + def search_alerts(self, username): + query = AlertQuery(AlertActor.eq(username)) + return self._sdk.alerts.search(query)["alerts"] + def get_alert_details(self, alert_id): res = self._sdk.alerts.get_details(alert_id)["alerts"] if not res: @@ -588,6 +592,27 @@ script: return_error(create_command_error_message(demisto.command(), e)) + def alert_search_command(client, args): + username = args["username"] + try: + alerts = client.search_alerts(username) + alert_context = [] + for alert in alerts: + alert_context.append(map_to_code42_alert_context(alert)) + readable_outputs = tableToMarkdown( + "Code42 Security Alert Search", + alert_context, + headers=SECURITY_ALERT_HEADERS, + ) + return ( + readable_outputs, + {"Code42.SecurityAlert": alert_context}, + alerts + ) + + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) + @logger def departingemployee_add_command(client, args): @@ -903,6 +928,7 @@ script: commands = { "code42-alert-get": alert_get_command, "code42-alert-resolve": alert_resolve_command, + "code42-alert-search": alert_search_command, "code42-securitydata-search": securitydata_search_command, "code42-departingemployee-add": departingemployee_add_command, "code42-departingemployee-remove": departingemployee_remove_command, @@ -1128,7 +1154,7 @@ script: - contextPath: Code42.SecurityAlert.Severity description: The severity of the alert. type: string - description: Retrieve alert details by alert ID + description: Retrieve alert details by alert ID. - name: code42-alert-resolve arguments: - name: id @@ -1139,6 +1165,37 @@ script: description: The alert ID of the resolved alert. type: string description: Resolves a Code42 Security alert. + - name: code42-alert-search + arguments: + - name: username + required: true + description: The username for the user to search alerts for. + outputs: + - contextPath: Code42.SecurityAlert.Username + description: The username associated with the alert. + type: string + - contextPath: Code42.SecurityAlert.Occurred + description: The timestamp when the alert occurred. + type: date + - contextPath: Code42.SecurityAlert.Description + description: The description of the alert. + type: string + - contextPath: Code42.SecurityAlert.ID + description: The alert ID. + type: string + - contextPath: Code42.SecurityAlert.Name + description: The alert rule name that generated the alert. + type: string + - contextPath: Code42.SecurityAlert.State + description: The alert state. + type: string + - contextPath: Code42.SecurityAlert.Type + description: The alert type. + type: string + - contextPath: Code42.SecurityAlert.Severity + description: The severity of the alert. + type: string + description: Search alerts by username. - name: code42-departingemployee-add arguments: - name: username @@ -1249,6 +1306,7 @@ script: type: string - contextPath: Code42.HighRiskEmployee.RiskTags description: Risk tags to associate with the High Risk Employee. + description: Add the given risk tags to the user with the given username. - name: code42-highriskemployee-remove-risk-tags arguments: - name: username @@ -1266,6 +1324,7 @@ script: type: string - contextPath: Code42.HighRiskEmployee.RiskTags description: Risk tags to disassociate from the High Risk Employee. + description: Remove the given risk tags from the user with the given username. dockerimage: demisto/py42:1.0.0.9242 isfetch: true runonce: false diff --git a/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/CHANGELOG.md b/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/CHANGELOG.md new file mode 100644 index 000000000000..63439c17f377 --- /dev/null +++ b/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/CHANGELOG.md @@ -0,0 +1,2 @@ +## [Unreleased] +- diff --git a/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/Code42GetDepartingEmployeeAlerts.py b/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/Code42GetDepartingEmployeeAlerts.py new file mode 100644 index 000000000000..8f428d0bfa92 --- /dev/null +++ b/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/Code42GetDepartingEmployeeAlerts.py @@ -0,0 +1,36 @@ +import demistomock as demisto +from CommonServerPython import * + + +def main(): + res = {"total": 0} + res_data = [] + top = demisto.args().get("top") or 10 + + try: + employees = demisto.executeCommand("code42-departingemployee-get-all", {})[0]["Contents"] + res["total"] = len(employees) + + # Get each employee on the Departing Employee List and their total alerts. + for employee in employees: + username = employee["userName"] + alerts = demisto.executeCommand("code42-alert-search", {"username": username})[0]["Contents"] + alerts_count = len(alerts) + + # Ignores employees without alerts + if alerts_count: + employee_res = {"Username": username, "Alerts Count": alerts_count} + res_data.append(employee_res) + + # Sort such that highest alert counts are first and get top. + res["data"] = sorted(res_data, key=lambda x: x["Alerts Count"], reverse=True)[:top] + demisto.results(res) + except Exception as e: + res["total"] = -1 + res["data"] = str(e) + + # Submit final results to Cortex XSOAR + demisto.results(res) + +if __name__ in ("__main__", "__builtin__", "builtins"): + main() diff --git a/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/Code42GetDepartingEmployeeAlerts.yml b/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/Code42GetDepartingEmployeeAlerts.yml new file mode 100644 index 000000000000..67d299bd84c3 --- /dev/null +++ b/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/Code42GetDepartingEmployeeAlerts.yml @@ -0,0 +1,24 @@ +args: +- defaultValue: 10 + description: To limit results to x number of employees with the highest alert count. + name: top + type: number +comment: Gets all departing employees and alerts for each. +commonfields: + id: 468c8e6f-6f50-486f-8cde-7dabe4cbeb2b + version: -1 +dependson: + must: + - Code42|||code42-departingemployee-get-all + - Code42|||code42-alerts-search +dockerimage: demisto/py42:1.0.0.9242 +enabled: true +name: Code42GetDepartingEmployeeAlerts +pswd: "" +runas: DBotWeakRole +runonce: false +script: '' +scripttarget: 0 +subtype: python3 +tags: ['dynamic-section', 'widget'] +type: python diff --git a/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/README.md b/Packs/Code42/Scripts/Code42GetDepartingEmployeeAlerts/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/Packs/Code42/Widgets/widget-Departing_Employee_Alerts.json b/Packs/Code42/Widgets/widget-Departing_Employee_Alerts.json new file mode 100644 index 000000000000..426a1ba23c79 --- /dev/null +++ b/Packs/Code42/Widgets/widget-Departing_Employee_Alerts.json @@ -0,0 +1,40 @@ +{ + "category": "", + "dataType": "scripts", + "dateRange": { + "fromDate": "0001-01-01T00:00:00Z", + "fromDateLicense": "0001-01-01T00:00:00Z", + "period": { + "by": "", + "byFrom": "days", + "byTo": "", + "field": "", + "fromValue": null, + "toValue": null + }, + "toDate": "0001-01-01T00:00:00Z" + }, + "id": "570ec235-7ee4-43ea-8fc7-eba94a0cca71", + "isPredefined": false, + "name": "Departing Employee Alerts", + "params": { + "tableColumns": [ + { + "displayed": true, + "isDefault": true, + "key": "Username" + }, + { + "displayed": true, + "isDefault": true, + "key": "Alerts Count" + } + ] + }, + "query": "Code42GetDepartingEmployeeAlerts", + "size": 0, + "sort": null, + "sortValues": null, + "version": -1, + "widgetType": "table" +} \ No newline at end of file