Skip to content

Commit

Permalink
SOARHELP-2878: Duplicate data coming in action 'list resources' (#14)
Browse files Browse the repository at this point in the history
* Commit with debugging codes and comments

* SOARHELP-2878: Handle max limit in all scenarios(last page, not last page)

* Update README.md

* SOARHELP-2878: Bigfix code

* SOARHELP-2878: Remove params argument, use params in query parameter

* Revert "SOARHELP-2878: Remove params argument, use params in query parameter"

This reverts commit f87605a.

* Change Min Phantom Version and copyright year

* Update README.md

* change version for installing in cloud instance(There is a bug in automation broker)

* Update README.md

* Change minor version and change number of resources per page to 100

* Update README.md

* remove unwanted variable

* updated error messages for test connectivity

* precommit changes

* removed unused library

* added documentation for sponsor account and release notes

* Update README.md

* Handle None value for items in ERS response

* Solve lint error

* Handle page limit and absence of SearchResult i the response

* Update debug message for 'Maximum limit reached'

* Bugfix in const file and validate_integer function. Update min_phantom_version

* Update README.md

* Update latest tested version

* Review comments - update const variable names, make code pattern consistency

* Update date of latest tested version

---------

Co-authored-by: splunk-soar-connectors-admin <admin@splunksoar>
Co-authored-by: aparekh-crest <[email protected]>
  • Loading branch information
3 people authored May 2, 2024
1 parent 6a6af2d commit 0b42f93
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
limitations under the License.
2 changes: 1 addition & 1 deletion NOTICE
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Splunk SOAR Cisco ISE
Copyright (c) 2014-2023 Splunk Inc.
Copyright (c) 2014-2024 Splunk Inc.

Third-party Software Attributions:

Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion __init__.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
12 changes: 6 additions & 6 deletions ciscoise.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -1921,12 +1921,12 @@
},
{
"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",
"input_file": "wheels/shared/xmltodict-0.12.0-py2.py3-none-any.whl"
}
]
}
}
}
149 changes: 104 additions & 45 deletions ciscoise_connector.py
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -16,6 +16,7 @@
#
# Phantom imports
import json
import sys

import phantom.app as phantom
import requests
Expand Down Expand Up @@ -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__()

Expand Down Expand Up @@ -116,14 +116,16 @@ 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
url = "{0}{1}".format(self._base_url, endpoint)
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()
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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):

Expand Down Expand Up @@ -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()
Expand All @@ -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))

Expand Down
Loading

0 comments on commit 0b42f93

Please sign in to comment.