diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 827b060..907435b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/phantomcyber/dev-cicd-tools - rev: v1.16 + rev: v1.17 hooks: - id: org-hook - id: package-app-dependencies diff --git a/LICENSE b/LICENSE index c9694ca..f477717 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (c) 2014-2023 Splunk Inc. + Copyright (c) 2014-2024 Splunk Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE index 734fbd3..96d5351 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Splunk SOAR Cisco ISE -Copyright (c) 2014-2023 Splunk Inc. +Copyright (c) 2014-2024 Splunk Inc. Third-party Software Attributions: diff --git a/README.md b/README.md index ef8dd95..4f2c963 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ # Cisco ISE Publisher: Splunk -Connector Version: 3.0.2 +Connector Version: 3.1.0 Product Vendor: Cisco Systems Product Name: Cisco ISE Product Version Supported (regex): "/([2].[67])|([3].[01])/" -Minimum Product Version: 5.1.0 +Minimum Product Version: 6.2.0 This app implements investigative and containment actions on a Cisco ISE device [comment]: # " File: README.md" -[comment]: # " Copyright (c) 2014-2023 Splunk Inc." +[comment]: # " Copyright (c) 2014-2024 Splunk Inc." [comment]: # "" [comment]: # " SPLUNK CONFIDENTIAL - Use or disclosure of this material in whole or in part" [comment]: # " without a valid written license from Splunk Inc. is PROHIBITED." @@ -52,7 +52,9 @@ This app implements investigative and containment actions on a Cisco ISE device - update resource - apply policy - create policy -3. An ISE node can assume any or all of the following personas: Administration, Policy Service, and +3. If resource is **Guest User** in resource related actions, it is required to use **Sponsor Account** credentials to access the GuestAPI, For creating sponsor account refer this document: [Set Up Admin and Sponsor Account for ERS](https://www.cisco.com/c/en/us/support/docs/security/identity-services-engine/215476-configure-ise-guest-accounts-with-rest-a.html) +4. Once you have internal user created from step #3, Add username and password in **ers_username** and **ers_password** in asset configuration respectively. +5. An ISE node can assume any or all of the following personas: Administration, Policy Service, and Monitoring. For detailed info: [Types of nodes](https://www.cisco.com/en/US/docs/security/ise/1.0/user_guide/ise10_dis_deploy.html#wp1123452) - All actions can run on Administration node. @@ -61,7 +63,7 @@ This app implements investigative and containment actions on a Cisco ISE device node - Actions quarantine device, unquarantine device, apply policy, clear policy, and terminate session can run on Policy Service node -4. For create resource action, user needs to provide valid json with required fields of that +6. For create resource action, user needs to provide valid json with required fields of that specified resource (For more details head over to [API Reference](https://developer.cisco.com/docs/identity-services-engine/v1/#!endpoint) ). Examples as below diff --git a/__init__.py b/__init__.py index 445e59b..3a49875 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ # File: __init__.py # -# Copyright (c) 2014-2023 Splunk Inc. +# Copyright (c) 2014-2024 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/ciscoise.json b/ciscoise.json index 127614d..58fcda5 100644 --- a/ciscoise.json +++ b/ciscoise.json @@ -5,20 +5,20 @@ "publisher": "Splunk", "type": "network security", "main_module": "ciscoise_connector.py", - "app_version": "3.0.2", + "app_version": "3.1.0", "utctime_updated": "2022-03-11T04:16:56.000000Z", "package_name": "phantom_ciscoise", "product_vendor": "Cisco Systems", "product_name": "Cisco ISE", "product_version_regex": "/([2].[67])|([3].[01])/", - "min_phantom_version": "5.1.0", + "min_phantom_version": "6.2.0", "logo": "logo_cisco.svg", "logo_dark": "logo_cisco_dark.svg", - "license": "Copyright (c) 2014-2023 Splunk Inc.", + "license": "Copyright (c) 2014-2024 Splunk Inc.", "python_version": "3", "fips_compliant": true, "latest_tested_version": [ - "Cisco ISE version 3.0.0.458, 22th Feb 2022" + "Cisco ISE version 3.0.0.458, 29 Apr 2024" ], "configuration": { "device": { @@ -1921,7 +1921,7 @@ }, { "module": "setuptools", - "input_file": "wheels/py3/setuptools-69.0.2-py3-none-any.whl" + "input_file": "wheels/py3/setuptools-69.2.0-py3-none-any.whl" }, { "module": "xmltodict", @@ -1929,4 +1929,4 @@ } ] } -} +} \ No newline at end of file diff --git a/ciscoise_connector.py b/ciscoise_connector.py index 67d32b8..bc4ccc1 100644 --- a/ciscoise_connector.py +++ b/ciscoise_connector.py @@ -1,6 +1,6 @@ # File: ciscoise_connector.py # -# Copyright (c) 2014-2023 Splunk Inc. +# Copyright (c) 2014-2024 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ # # Phantom imports import json +import sys import phantom.app as phantom import requests @@ -49,7 +50,6 @@ class CiscoISEConnector(BaseConnector): ACTION_ID_DELETE_POLICY = "delete_policy" def __init__(self): - # Call the BaseConnectors init first super(CiscoISEConnector, self).__init__() @@ -116,7 +116,7 @@ def make_another_call(*args, **kwargs): return make_another_call - def _call_ers_api(self, endpoint, action_result, data=None, allow_unknown=True, method="get", try_ha_device=False): + def _call_ers_api(self, endpoint, action_result, data=None, allow_unknown=True, method="get", try_ha_device=False, params=None): auth_method = self._ers_auth or self._auth if not auth_method: return action_result.set_status(phantom.APP_ERROR, CISCOISE_ERS_CRED_MISSING), None @@ -124,6 +124,8 @@ def _call_ers_api(self, endpoint, action_result, data=None, allow_unknown=True, if try_ha_device: url = "{0}{1}".format(self._ha_device_url, endpoint) + self.debug_print("url for calling an ERS API: {}".format(url)) + ret_data = None config = self.get_config() @@ -140,8 +142,10 @@ def _call_ers_api(self, endpoint, action_result, data=None, allow_unknown=True, json=data, verify=verify, headers=headers, - auth=auth_method + auth=auth_method, + params=params ) + except Exception as e: self.debug_print("Exception occurred: {}".format(e)) return action_result.set_status(phantom.APP_ERROR, CISCOISE_ERROR_REST_API, e), ret_data @@ -424,44 +428,43 @@ def _terminate_session(self, param): return action_result.set_status(phantom.APP_SUCCESS, CISCOISE_SUCC_SESSION_TERMINATED) - def _paginator(self, endpoint, action_result, payload=None, limit=None): + def _paginator(self, endpoint, action_result, limit=None): items_list = list() - - if not payload: - payload = {} - - page = 1 - payload["size"] = DEFAULT_MAX_RESULTS - payload["page"] = page + params = {} + if limit: + params["size"] = min(DEFAULT_MAX_RESULTS, limit) + else: + params["size"] = DEFAULT_MAX_RESULTS while True: - ret_val, items = self._call_ers_api(endpoint, action_result, data=payload) - + ret_val, items = self._call_ers_api(endpoint, action_result, params=params) if phantom.is_fail(ret_val): + self.debug_print("Call to ERS API Failed") return None + items_from_page = items.get("SearchResult", {}).get("resources", []) - items_list.extend(items.get("SearchResult", {}).get("resources")) + items_list.extend(items_from_page) + self.debug_print("Retrieved {} records from the endpoint {}".format(len(items_from_page), endpoint)) + + next_page_dict = items.get("SearchResult", {}).get("nextPage") if limit and len(items_list) >= limit: + self.debug_print("Maximum limit reached") return items_list[:limit] - - if len(items.get("SearchResult", {}).get("resources")) < DEFAULT_MAX_RESULTS: - break - - if len(items_list) == items.get("SearchResult", {}).get("total"): - break - - page = page + 1 - payload["page"] = page - - return items_list + else: + if not next_page_dict: + self.debug_print("No more records left to retrieve") + return items_list + else: + endpoint = next_page_dict.get("href").replace(self._base_url, "") + self.debug_print("Next page available") def _list_resources(self, param): action_result = self.add_action_result(ActionResult(dict(param))) resource = self._map_resource_type(param["resource"], action_result) - ret_val, max_result = self._validate_integers(action_result, param.get("max_results"), 'max_result') + ret_val, max_result = self._validate_integers(action_result, param.get("max_results"), 'max results') if phantom.is_fail(ret_val): return action_result.get_status() @@ -707,30 +710,39 @@ def _test_connectivity_to_device(self, base_url, verify=True): auth=self._auth, verify=verify) except Exception as e: - self.debug_print("Exception is test connectivity: {}".format(e)) - return self.set_status_save_progress(phantom.APP_ERROR, CISCOISE_ERROR_TEST_CONNECTIVITY_FAILED) + return False, str(e) if resp.status_code == 200: - return self.set_status_save_progress(phantom.APP_SUCCESS, CISCOISE_SUCC_TEST_CONNECTIVITY_PASSED) - else: - return self.set_status_save_progress( - phantom.APP_ERROR, - CISCOISE_TEST_CONNECTIVITY_FAILED_ERROR_CODE, - code=resp.status_code - ) + return True, '' - def _test_connectivity(self, param): + return False, resp.text + def _test_connectivity(self, param): + action_result = self.add_action_result(ActionResult(dict(param))) config = self.get_config() verify = config[phantom.APP_JSON_VERIFY] - self.save_progress("Connecting to first device") - result = self._test_connectivity_to_device(self._base_url, verify) + result, message = self._test_connectivity_to_device(self._base_url, verify) + + if not result: + self.save_progress("Error occurred while connecting to primary device") + self.save_progress(str(message)) + self.save_progress(CISCOISE_ERROR_TEST_CONNECTIVITY_FAILED_PRIMARY_DEVICE) + action_result.set_status(phantom.APP_ERROR) + else: + self.save_progress(CISCOISE_SUCC_TEST_CONNECTIVITY_PASSED_1) + action_result.set_status(phantom.APP_SUCCESS, CISCOISE_SUCC_TEST_CONNECTIVITY_PASSED_1) if self._ha_device: - self.save_progress("Connecting to second device") - result = self._test_connectivity_to_device(self._ha_device_url, verify) + result, message = self._test_connectivity_to_device(self._ha_device_url, verify) - return result + if not result: + self.save_progress("Error occurred while connecting to high availability device") + self.save_progress(str(message)) + self.save_progress(CISCOISE_ERROR_TEST_CONNECTIVITY_FAILED_HA_DEVICE) + else: + self.save_progress(CISCOISE_SUCC_TEST_CONNECTIVITY_PASSED_2) + + return action_result.get_status() def handle_action(self, param): @@ -775,17 +787,60 @@ def handle_action(self, param): return result -if __name__ == "__main__": +if __name__ == '__main__': - import sys + import argparse import pudb pudb.set_trace() + argparser = argparse.ArgumentParser() + + argparser.add_argument('input_test_json', help='Input Test JSON file') + argparser.add_argument('-u', '--username', help='username', required=False) + argparser.add_argument('-p', '--password', help='password', required=False) + argparser.add_argument('-v', '--verify', action='store_true', help='verify', required=False, default=False) + + args = argparser.parse_args() + session_id = None + + username = args.username + password = args.password + verify = args.verify + + if username is not None and password is None: + + # User specified a username but not a password, so ask + import getpass + password = getpass.getpass("Password: ") + + if username and password: + login_url = BaseConnector._get_phantom_base_url() + "login" + try: + print("Accessing the Login page") + r = requests.get(login_url, verify=verify, timeout=30) + csrftoken = r.cookies['csrftoken'] + + data = dict() + data['username'] = username + data['password'] = password + data['csrfmiddlewaretoken'] = csrftoken + + headers = dict() + headers['Cookie'] = 'csrftoken=' + csrftoken + headers['Referer'] = login_url + + print("Logging into Platform to get the session id") + r2 = requests.post(login_url, verify=verify, data=data, headers=headers, timeout=30) + session_id = r2.cookies['sessionid'] + except Exception as e: + print("Unable to get session id from the platfrom. Error: " + str(e)) + sys.exit(1) + if len(sys.argv) < 2: print("No test json specified as input") - sys.exit(1) + sys.exit(0) with open(sys.argv[1]) as f: in_json = f.read() @@ -794,6 +849,10 @@ def handle_action(self, param): connector = CiscoISEConnector() connector.print_progress_message = True + + if session_id is not None: + in_json['user_session_token'] = session_id + ret_val = connector._handle_action(json.dumps(in_json), None) print(json.dumps(json.loads(ret_val), indent=4)) diff --git a/ciscoise_consts.py b/ciscoise_consts.py index dd95e0c..4f6fccd 100644 --- a/ciscoise_consts.py +++ b/ciscoise_consts.py @@ -1,6 +1,6 @@ # File: ciscoise_consts.py # -# Copyright (c) 2014-2023 Splunk Inc. +# Copyright (c) 2014-2024 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -33,9 +33,11 @@ ERS_POLICIES = ":9060/ers/config/ancpolicy" # Error/Success -CISCOISE_ERROR_TEST_CONNECTIVITY_FAILED = "Test connectivity failed" +CISCOISE_ERROR_TEST_CONNECTIVITY_FAILED_PRIMARY_DEVICE = "Test connectivity failed for primary device" +CISCOISE_ERROR_TEST_CONNECTIVITY_FAILED_HA_DEVICE = "Test connectivity failed for high availability device" CISCOISE_TEST_CONNECTIVITY_FAILED_ERROR_CODE = "Test connectivity failed with status code: '{code}'" -CISCOISE_SUCC_TEST_CONNECTIVITY_PASSED = "Test connectivity passed" +CISCOISE_SUCC_TEST_CONNECTIVITY_PASSED_1 = "Test connectivity passed for primary device" +CISCOISE_SUCC_TEST_CONNECTIVITY_PASSED_2 = "Test connectivity passed for second device" CISCOISE_ERROR_REST_API = "REST Api error" CISCOISE_REST_API_ERROR_CODE = "REST Api error with status code: {code}, Message from server: {message}" CISCOISE_ERROR_UNABLE_TO_PARSE_REPLY = "Parsing error, Unable to convert xml reply to json" @@ -48,10 +50,10 @@ CISCOISE_SUCC_LIST_ENDPOINTS = "{0} Endpoints found" CISCOISE_SUCC_GET_ENDPOINT = "Endpoint found" CISCOISE_SUCC_UPDATE_ENDPOINT = "Endpoint updated" -CISCOISE_ERROR_INVALID_PARAM = "Please provide a non-zero positive integer in {param}" +CISCOISE_ERROR_INVALID_PARAM = "Please provide a non-zero positive integer in '{}'" CISCOISE_MAP_IP_ABSENT_ERROR = "Please provide either mac address or ip address" CISCOISE_ERS_CRED_MISSING = "ERS credentials in asset configuration are required for this action" -DEFAULT_MAX_RESULTS = 7 +DEFAULT_MAX_RESULTS = 100 # Json reply schema IS_MAC_QUARAN_RESP_SCHEMA = { diff --git a/manual_readme_content.md b/manual_readme_content.md index 4cc9e20..be68391 100644 --- a/manual_readme_content.md +++ b/manual_readme_content.md @@ -1,5 +1,5 @@ [comment]: # " File: README.md" -[comment]: # " Copyright (c) 2014-2023 Splunk Inc." +[comment]: # " Copyright (c) 2014-2024 Splunk Inc." [comment]: # "" [comment]: # " SPLUNK CONFIDENTIAL - Use or disclosure of this material in whole or in part" [comment]: # " without a valid written license from Splunk Inc. is PROHIBITED." @@ -40,7 +40,9 @@ - update resource - apply policy - create policy -3. An ISE node can assume any or all of the following personas: Administration, Policy Service, and +3. If resource is **Guest User** in resource related actions, it is required to use **Sponsor Account** credentials to access the GuestAPI, For creating sponsor account refer this document: [Set Up Admin and Sponsor Account for ERS](https://www.cisco.com/c/en/us/support/docs/security/identity-services-engine/215476-configure-ise-guest-accounts-with-rest-a.html) +4. Once you have internal user created from step #3, Add username and password in **ers_username** and **ers_password** in asset configuration respectively. +5. An ISE node can assume any or all of the following personas: Administration, Policy Service, and Monitoring. For detailed info: [Types of nodes](https://www.cisco.com/en/US/docs/security/ise/1.0/user_guide/ise10_dis_deploy.html#wp1123452) - All actions can run on Administration node. @@ -49,7 +51,7 @@ node - Actions quarantine device, unquarantine device, apply policy, clear policy, and terminate session can run on Policy Service node -4. For create resource action, user needs to provide valid json with required fields of that +6. For create resource action, user needs to provide valid json with required fields of that specified resource (For more details head over to [API Reference](https://developer.cisco.com/docs/identity-services-engine/v1/#!endpoint) ). Examples as below diff --git a/release_notes/3.1.0.md b/release_notes/3.1.0.md new file mode 100644 index 0000000..76725e6 --- /dev/null +++ b/release_notes/3.1.0.md @@ -0,0 +1 @@ +* Fixed pagination logic for 'list resources' action [PAPP-33229] \ No newline at end of file diff --git a/wheels/py3/setuptools-69.0.2-py3-none-any.whl b/wheels/py3/setuptools-69.2.0-py3-none-any.whl similarity index 68% rename from wheels/py3/setuptools-69.0.2-py3-none-any.whl rename to wheels/py3/setuptools-69.2.0-py3-none-any.whl index a609341..d02fe9f 100644 Binary files a/wheels/py3/setuptools-69.0.2-py3-none-any.whl and b/wheels/py3/setuptools-69.2.0-py3-none-any.whl differ