From c71bbacb25c81572854a90fb0fc667ba861772d0 Mon Sep 17 00:00:00 2001 From: Kiran Chaudhary Date: Thu, 9 Jul 2020 21:33:14 +0530 Subject: [PATCH 1/5] Add support for exposure type exists criteria when no exposure type is specified --- Packs/Code42/Integrations/Code42/Code42.py | 6 ++- Packs/Code42/Integrations/Code42/Code42.yml | 5 ++- .../Code42/Integrations/Code42/Code42_test.py | 44 ++++++++++++++++--- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 3497946763f4..b2c6996adbda 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -421,8 +421,9 @@ def build_query_payload(args): search_args.append_result(_hash, _create_hash_filter) search_args.append_result(hostname, OSHostname.eq) search_args.append_result(username, DeviceUsername.eq) - search_args.append_result(exposure, _create_exposure_filter) + exposure_filter = _create_exposure_filter(exposure) + search_args.append(exposure_filter) query = search_args.to_all_query() LOG("File Event Query: {}".format(str(query))) return query @@ -447,7 +448,8 @@ def _create_exposure_filter(exposure_arg): # Because the CLI can't accept lists, convert the args to a list if the type is string. if isinstance(exposure_arg, str): exposure_arg = exposure_arg.split(",") - return ExposureType.is_in(exposure_arg) + return ExposureType.is_in(exposure_arg) + return ExposureType.exists() def _create_category_filter(file_type): diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index b16f1aa93348..cd455ff609c3 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -78,8 +78,9 @@ script: secret: false - auto: PREDEFINED default: false - description: Exposure types to search for. Can be "RemovableMedia", "ApplicationRead", - "CloudStorage", "IsPublic", "SharedViaLink", "SharedViaDomain", or "OutsideTrustedDomains". + description: Exposure types to search for. Defaults to all. i.e Exposure type + exists. Value can be "RemovableMedia", "ApplicationRead", "CloudStorage", + "IsPublic", "SharedViaLink", "SharedViaDomain", or "OutsideTrustedDomains". isArray: true name: exposure predefined: diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 43ea9043687e..7ab9d5591c41 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -45,6 +45,14 @@ "results": 50, } +MOCK_SECURITY_DATA_SEARCH_QUERY_WITHOUT_EXPOSURE_TYPE = { + "hash": "d41d8cd98f00b204e9800998ecf8427e", + "hostname": "DESKTOP-0001", + "username": "user3@example.com", + "results": 50, +} + + MOCK_SECURITY_EVENT_RESPONSE = """ { "totalCount":3, @@ -70,8 +78,8 @@ "deviceUserName":"test@example.com", "osHostName":"HOSTNAME", "domainName":"host.docker.internal", - "publicIpAddress":"162.222.47.183", - "privateIpAddresses":["172.20.128.36","127.0.0.1"], + "publicIpAddress":"255.255.255.255", + "privateIpAddresses":["255.255.255.255","127.0.0.1"], "deviceUid":"935873453596901068", "userUid":"912098363086307495", "actor":null, @@ -134,7 +142,7 @@ "deviceUserName":"test@example.com", "osHostName":"TEST'S MAC", "domainName":"host.docker.internal", - "publicIpAddress":"162.222.47.183", + "publicIpAddress":"255.255.255.255", "privateIpAddresses":["127.0.0.1"], "deviceUid":"935873453596901068", "userUid":"912098363086307495", @@ -198,7 +206,7 @@ "deviceUserName":"test@example.com", "osHostName":"Test's Windows", "domainName":"host.docker.internal", - "publicIpAddress":"162.222.47.183", + "publicIpAddress":"255.255.255.255", "privateIpAddresses":["0:0:0:0:0:0:0:1","127.0.0.1"], "deviceUid":"935873453596901068", "userUid":"912098363086307495", @@ -248,7 +256,7 @@ MOCK_CODE42_EVENT_CONTEXT = [ { "ApplicationTabURL": "example.com", - "DevicePrivateIPAddress": ["172.20.128.36", "127.0.0.1"], + "DevicePrivateIPAddress": ["255.255.255.255", "127.0.0.1"], "DeviceUsername": "test@example.com", "EndpointID": "935873453596901068", "EventID": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", @@ -1380,7 +1388,7 @@ def test_departingemployee_get_all_command_when_no_employees( no_employees_response ) client = create_client(code42_departing_employee_mock) - cmd_res = departingemployee_get_all_command(client,{}) + cmd_res = departingemployee_get_all_command(client, {}) assert cmd_res.outputs_prefix == "Code42.DepartingEmployee" assert cmd_res.outputs_key_field == "UserID" assert cmd_res.raw_response == {} @@ -1792,3 +1800,27 @@ def test_fetch_incidents_fetch_limit(code42_fetch_incidents_mock): assert len(incidents) == 1 assert next_run["last_fetch"] assert not remaining_incidents + + +def test_security_data_search_command_searches_exposure_exists_when_no_exposure_type_is_specified( + code42_file_events_mock +): + client = create_client(code42_file_events_mock) + cmd_res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY_WITHOUT_EXPOSURE_TYPE) + code42_res = cmd_res[0] + file_res = cmd_res[1] + + assert code42_res.outputs_prefix == "Code42.SecurityData" + assert code42_res.outputs_key_field == "EventID" + assert file_res.outputs_prefix == "File" + + actual_query = code42_file_events_mock.securitydata.search_file_events.call_args[0][0] + filter_groups = json.loads(str(actual_query))["groups"] + + # Assert that the correct query gets made + assert len(filter_groups) == 4 + for i in range(0, len(filter_groups)): + _filter = filter_groups[i]["filters"][0] + if _filter["term"] == "exposure": + assert _filter["operator"] == 'EXISTS' + assert _filter["value"] is None From 4c2053b17e982753efc40b6c63581d792c5c0722 Mon Sep 17 00:00:00 2001 From: Alan Grgic Date: Thu, 9 Jul 2020 12:06:18 -0500 Subject: [PATCH 2/5] adjust test --- Packs/Code42/Integrations/Code42/Code42_test.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 7ab9d5591c41..31d27bbf3051 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1815,12 +1815,21 @@ def test_security_data_search_command_searches_exposure_exists_when_no_exposure_ assert file_res.outputs_prefix == "File" actual_query = code42_file_events_mock.securitydata.search_file_events.call_args[0][0] + + # Assert that the correct query gets made filter_groups = json.loads(str(actual_query))["groups"] + expected_query_items = [ + ("md5Checksum", "d41d8cd98f00b204e9800998ecf8427e"), + ("osHostName", "DESKTOP-0001"), + ("deviceUserName", "user3@example.com"), + ("exposure", None), + ] # Assert that the correct query gets made - assert len(filter_groups) == 4 + assert len(filter_groups) == len(expected_query_items) for i in range(0, len(filter_groups)): _filter = filter_groups[i]["filters"][0] - if _filter["term"] == "exposure": - assert _filter["operator"] == 'EXISTS' - assert _filter["value"] is None + assert _filter["term"] == expected_query_items[i][0] + assert _filter["value"] == expected_query_items[i][1] + + assert len(filter_groups) == 4 From af7c457ab78c49a755af9b0f99eff7b1122e0ef1 Mon Sep 17 00:00:00 2001 From: Kiran Chaudhary Date: Fri, 10 Jul 2020 10:16:58 +0530 Subject: [PATCH 3/5] To search for all exposure types specify all option --- Packs/Code42/Integrations/Code42/Code42.py | 10 +-- Packs/Code42/Integrations/Code42/Code42.yml | 62 ++++++++++--------- .../Code42/Integrations/Code42/Code42_test.py | 60 +++++++++++++++++- 3 files changed, 95 insertions(+), 37 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 1257f9672638..13ff1850b593 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -495,9 +495,8 @@ def build_query_payload(args): search_args.append_result(_hash, _create_hash_filter) search_args.append_result(hostname, OSHostname.eq) search_args.append_result(username, DeviceUsername.eq) + search_args.append_result(exposure, _create_exposure_filter) - exposure_filter = _create_exposure_filter(exposure) - search_args.append(exposure_filter) query = search_args.to_all_query() LOG("File Event Query: {}".format(str(query))) return query @@ -521,9 +520,10 @@ def _create_hash_filter(hash_arg): def _create_exposure_filter(exposure_arg): # Because the CLI can't accept lists, convert the args to a list if the type is string. if isinstance(exposure_arg, str): - exposure_arg = exposure_arg.split(",") - return ExposureType.is_in(exposure_arg) - return ExposureType.exists() + exposure_arg = [arg.strip() for arg in exposure_arg.split(",")] + if "All" in exposure_arg: + return ExposureType.exists() + return ExposureType.is_in(exposure_arg) def _create_category_filter(file_type): diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index 324f850fdcdd..29dac23baa4e 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -78,12 +78,14 @@ script: secret: false - auto: PREDEFINED default: false - description: Exposure types to search for. Defaults to all. i.e Exposure type - exists. Value can be "RemovableMedia", "ApplicationRead", "CloudStorage", - "IsPublic", "SharedViaLink", "SharedViaDomain", or "OutsideTrustedDomains". + description: Exposure types to search for. Values can be "All", "RemovableMedia", + "ApplicationRead", "CloudStorage", "IsPublic", "SharedViaLink", "SharedViaDomain", + or "OutsideTrustedDomains". When "All" is specified with other types, other + types would be ignored and filter rule for all types would be applied. isArray: true name: exposure predefined: + - All - RemovableMedia - ApplicationRead - CloudStorage @@ -658,7 +660,8 @@ script: required: true secret: false - default: false - description: The name of the legal hold matter to which to which the user will be added. + description: The name of the legal hold matter to which to which the user will + be added. isArray: false name: mattername required: true @@ -681,35 +684,36 @@ script: description: A name for a Code42 legal hold matter. type: String - arguments: - - default: false - description: The username of the user to remove from the given legal hold matter. - isArray: false - name: username - required: true - secret: false - - default: false - description: The name of the legal hold matter from which to which the user will be removed. - isArray: false - name: mattername - required: true - secret: false + - default: false + description: The username of the user to remove from the given legal hold matter. + isArray: false + name: username + required: true + secret: false + - default: false + description: The name of the legal hold matter from which to which the user + will be removed. + isArray: false + name: mattername + required: true + secret: false deprecated: false description: Removes a Code42 user from a legal hold matter. execution: false name: code42-legalhold-remove-user outputs: - - contextPath: Code42.LegalHold.UserID - description: The ID of a Code42 user. - type: Unknown - - contextPath: Code42.LegalHold.MatterID - description: The ID of a Code42 legal hold matter. - type: String - - contextPath: Code42.LegalHold.Username - description: A username for a Code42 user. - type: String - - contextPath: Code42.LegalHold.MatterName - description: A name for a Code42 legal hold matter. - type: String + - contextPath: Code42.LegalHold.UserID + description: The ID of a Code42 user. + type: Unknown + - contextPath: Code42.LegalHold.MatterID + description: The ID of a Code42 legal hold matter. + type: String + - contextPath: Code42.LegalHold.Username + description: A username for a Code42 user. + type: String + - contextPath: Code42.LegalHold.MatterName + description: A name for a Code42 legal hold matter. + type: String - arguments: - default: false description: Either the SHA256 or MD5 hash of the file. @@ -727,7 +731,7 @@ script: description: Downloads a file from Code42 servers. execution: false name: code42-download-file - dockerimage: demisto/py42:1.0.0.9653 + dockerimage: demisto/py42:1.0.0.9801 feed: false isfetch: true longRunning: false diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 455aca59bed0..d54d8e54c3d5 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -54,6 +54,22 @@ "results": 50, } +MOCK_SECURITY_DATA_SEARCH_QUERY_EXPOSURE_TYPE_ALL = { + "hash": "d41d8cd98f00b204e9800998ecf8427e", + "hostname": "DESKTOP-0001", + "username": "user3@example.com", + "exposure": "All", + "results": 50, +} + +MOCK_SECURITY_DATA_SEARCH_QUERY_EXPOSURE_TYPE_ALL_WITH_OTHERS = { + "hash": "d41d8cd98f00b204e9800998ecf8427e", + "hostname": "DESKTOP-0001", + "username": "user3@example.com", + "exposure": "ApplicationRead, All", + "results": 50, +} + MOCK_SECURITY_DATA_SEARCH_QUERY_WITHOUT_EXPOSURE_TYPE = { "hash": "d41d8cd98f00b204e9800998ecf8427e", "hostname": "DESKTOP-0001", @@ -2016,11 +2032,17 @@ def test_fetch_incidents_fetch_limit(code42_fetch_incidents_mock): assert not remaining_incidents -def test_security_data_search_command_searches_exposure_exists_when_no_exposure_type_is_specified( - code42_file_events_mock +@pytest.mark.parametrize( + "query", + [MOCK_SECURITY_DATA_SEARCH_QUERY_EXPOSURE_TYPE_ALL, + MOCK_SECURITY_DATA_SEARCH_QUERY_EXPOSURE_TYPE_ALL_WITH_OTHERS + ] +) +def test_security_data_search_command_searches_exposure_exists_when_all_is_specified( + code42_file_events_mock, query ): client = create_client(code42_file_events_mock) - cmd_res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY_WITHOUT_EXPOSURE_TYPE) + cmd_res = securitydata_search_command(client, query) code42_res = cmd_res[0] file_res = cmd_res[1] @@ -2047,3 +2069,35 @@ def test_security_data_search_command_searches_exposure_exists_when_no_exposure_ assert _filter["value"] == expected_query_items[i][1] assert len(filter_groups) == 4 + + +def test_security_data_search_command_searches_exposure_exists_when_no_exposure_type_is_specified( + code42_file_events_mock, +): + client = create_client(code42_file_events_mock) + cmd_res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY_WITHOUT_EXPOSURE_TYPE) + code42_res = cmd_res[0] + file_res = cmd_res[1] + + assert code42_res.outputs_prefix == "Code42.SecurityData" + assert code42_res.outputs_key_field == "EventID" + assert file_res.outputs_prefix == "File" + + actual_query = code42_file_events_mock.securitydata.search_file_events.call_args[0][0] + + # Assert that the correct query gets made + filter_groups = json.loads(str(actual_query))["groups"] + expected_query_items = [ + ("md5Checksum", "d41d8cd98f00b204e9800998ecf8427e"), + ("osHostName", "DESKTOP-0001"), + ("deviceUserName", "user3@example.com"), + ] + + # Assert that the correct query gets made + assert len(filter_groups) == len(expected_query_items) + for i in range(0, len(filter_groups)): + _filter = filter_groups[i]["filters"][0] + assert _filter["term"] == expected_query_items[i][0] + assert _filter["value"] == expected_query_items[i][1] + + assert len(filter_groups) == 3 From ac93260fd7f01bf26bb986090a4210f82d2caca3 Mon Sep 17 00:00:00 2001 From: Kiran Chaudhary Date: Fri, 10 Jul 2020 10:56:47 +0530 Subject: [PATCH 4/5] Fix --- Packs/Code42/Integrations/Code42/Code42.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 13ff1850b593..81a807b67f2b 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -519,10 +519,9 @@ def _create_hash_filter(hash_arg): def _create_exposure_filter(exposure_arg): # Because the CLI can't accept lists, convert the args to a list if the type is string. - if isinstance(exposure_arg, str): - exposure_arg = [arg.strip() for arg in exposure_arg.split(",")] - if "All" in exposure_arg: - return ExposureType.exists() + exposure_arg = [arg.strip() for arg in exposure_arg.split(",")] + if "All" in exposure_arg: + return ExposureType.exists() return ExposureType.is_in(exposure_arg) From abdf253747bd739698a3aa725c643c589d42662d Mon Sep 17 00:00:00 2001 From: Kiran Chaudhary Date: Fri, 10 Jul 2020 19:57:09 +0530 Subject: [PATCH 5/5] Added changelog --- Packs/Code42/Integrations/Code42/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Packs/Code42/Integrations/Code42/CHANGELOG.md b/Packs/Code42/Integrations/Code42/CHANGELOG.md index 1fdf276feb35..44e1df80a27c 100644 --- a/Packs/Code42/Integrations/Code42/CHANGELOG.md +++ b/Packs/Code42/Integrations/Code42/CHANGELOG.md @@ -21,6 +21,7 @@ - Fixed bug in Fetch where errors occurred when `FileCategory` was set to include only one category. - Fixed bug in Fetch to handle new Code42 exposure type **Outside trusted domains**. - Improved Fetch to handle unsupported exposure types better. +- Added option to specify `All` in `exposure` argument in `search-securitydata` command to fetch all results with exfiltration. ## [20.3.3] - 2020-03-18 #### New Integration