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..448c407 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. @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under 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/unreleased.md b/release_notes/unreleased.md index fbcb2fd..f3a0670 100644 --- a/release_notes/unreleased.md +++ b/release_notes/unreleased.md @@ -1 +1,2 @@ **Unreleased** +* Fixed pagination logic for 'list resources' action [PAPP-33229] 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