From 26a7be9708267add112730687ff3e66c55a5d291 Mon Sep 17 00:00:00 2001 From: Jason DeMelo Date: Fri, 7 Jan 2022 10:30:00 -0800 Subject: [PATCH 01/21] Generate README.md content based on data from app JSON and/or rename readme.md -> README.md --- README.md | 621 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 616 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6367564..690d54b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,620 @@ -# Splunk> Phantom +[comment]: # "Auto-generated SOAR connector documentation" +# Cisco ISE -Welcome to the open-source repository for Splunk> Phantom's ciscoise App. +Publisher: Splunk +Connector Version: 2\.0\.5 +Product Vendor: Cisco Systems +Product Name: Cisco ISE +Product Version Supported (regex): "/\(\[2\]\.\[67\]\)\|\(\[3\]\.\[01\]\)/" +Minimum Product Version: 5\.0\.0 -Please have a look at our [Contributing Guide](https://github.com/Splunk-SOAR-Apps/.github/blob/main/.github/CONTRIBUTING.md) if you are interested in contributing, raising issues, or learning more about open-source Phantom apps. +This app implements investigative and containment actions on a Cisco ISE device -## Legal and License +[comment]: # " File: readme.md" +[comment]: # " Copyright (c) 2014-2021 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." +[comment]: # "" +[comment]: # " pragma: allowlist secret " +[comment]: # " pragma: allowlist secret " +[comment]: # " pragma: allowlist secret " +## Getting ERS credentials -This Phantom App is licensed under the Apache 2.0 license. Please see our [Contributing Guide](https://github.com/Splunk-SOAR-Apps/.github/blob/main/.github/CONTRIBUTING.md#legal-notice) for further details. +1. ### Enable Ers + + ERS uses on HTTPS port 9060 which is by default closed. Clients trying to access this port + without enabling ERS first, will face a timeout from the server. Therefore, the first + requirement is to enable ERS from the Cisco ISE admin UI. Go to **Administration \> Settings \> + ERS Settings** and enable the Enable ERS for Read/Write radio button + +2. ### Creating ERS Admin + + Go to **Administration \> Settings \> ERS Settings** and then from the panel on the left select + **Admin Users** under administrators. Now add an account by clicking **Add \> Create an admin + user** . Then enter name and password and select **ERS Admin** in Admin Group and then press + save. + +## Note + +1. Quarantine device and Unquarantine device actions may not work properly sometimes. Apply policy + and Clear policy with policy type QUARANTINE are recommended to use +2. ERS credentials are required for actions list endpoints, get device info, update device info, + get resources, delete resource, create resource, update resource, apply policy and create policy +3. 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. + - Actions create resource, update resource, delete resource, list resource, get resources, + list sessions, update device info, get device info, and list endpoints can run on Monitoring + 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 + specified resource (For more details head over to [API + Reference](https://developer.cisco.com/docs/identity-services-engine/v1/#!endpoint) ). Examples + as below + - Endpoint + + { + "ERSEndPoint": { + "name": "name", + "description": "MyEndpoint", + "mac": "11:22:33:44:55:66" + } + } + + + - Endpoint identity groups + + { + "EndPointGroup": { + "name": "Cisco-Meraki-Device", + "description": "Identity Group for Profile: Cisco-Meraki-Device", + "systemDefined": "true" + } + } + + + - Guest users + + { + "GuestUser": { + "name": "guestUser", + "guestInfo": { + "userName": "DS3ewdsa34wWE", + "password": "asdlkj324ew", + "enabled": true + }, + "guestAccessInfo": { + "validDays": 90 + } + } + } + + + - User identity groups + + { + "IdentityGroup": { + "name": "GuestType_Weekly (default)", + "parent": "NAC Group:NAC:IdentityGroups:User Identity Groups" + } + } + + + - Internal users + + { + "InternalUser": { + "name": "name", + "enabled": true, + "password": "*******", + "changePassword": true, + "passwordIDStore": "Internal Users" + } + } + + + - Network devices + + { + "NetworkDevice": { + "name": "ISE_EST_Local_Host", + "authenticationSettings": { + "enableKeyWrap": true, + "enableMultiSecret": true, + "keyEncryptionKey": 1234567890123456, + "keyInputFormat": "ASCII" + }, + "coaPort": 0, + "snmpsettings": { + "pollingInterval": 3600, + "linkTrapQuery": true, + "macTrapQuery": true, + "originatingPolicyServicesNode": "Auto" + }, + "trustsecsettings": { + "deviceAuthenticationSettings": {}, + "sgaNotificationAndUpdates": {}, + "deviceConfigurationDeployment": {}, + "pushIdSupport": false + }, + "tacacsSettings": { + "sharedSecret": "aaa" + }, + "profileName": "Cisco", + "NetworkDeviceIPList": [ + { + "ipaddress": "127.0.0.1", + "mask": 32 + } + ] + } + } + + + - Network device groups + + { + "NetworkDeviceGroup": { + "name": "Device Type#All Device Types" + } + } + + + - Security groups + + { + "Sgt": { + "name": "Employees", + "value": 4 + } + } + + +## Port Information + +The app uses HTTP/ HTTPS protocol for communicating with the Cisco ISE server. Below are the default +ports used by Splunk SOAR. + +| Service Name | Transport Protocol | Port | +|--------------|--------------------|------| +| http | tcp | 80 | +| https | tcp | 443 | + + +### Configuration Variables +The below configuration variables are required for this Connector to operate. These variables are specified when configuring a Cisco ISE asset in SOAR. + +VARIABLE | REQUIRED | TYPE | DESCRIPTION +-------- | -------- | ---- | ----------- +**device** | required | string | Device IP/Hostname +**ha\_device** | optional | string | Device IP/Hostname for a High Availability node if available +**username** | required | string | Username +**password** | required | password | Password +**ers\_user** | optional | string | Username for ERS APIs +**ers\_password** | optional | password | Password for ERS APIs +**verify\_server\_cert** | optional | boolean | Verify server certificate + +### Supported Actions +[test connectivity](#action-test-connectivity) - Validate the asset configuration for connectivity\. This action logs into the device using a REST API call to check the connection and credentials +[list endpoints](#action-list-endpoints) - List the endpoints configured on the system +[get device info](#action-get-device-info) - Get information about a specific endpoint +[update device info](#action-update-device-info) - Update information or attributes for a specific endpoint +[list sessions](#action-list-sessions) - List the sessions currently available on the Monitoring node +[quarantine device](#action-quarantine-device) - Quarantine the device +[unquarantine device](#action-unquarantine-device) - Unquarantine the device +[terminate session](#action-terminate-session) - Terminate sessions +[list resources](#action-list-resources) - Lists all the resources configured on the system of a particular resource +[get resources](#action-get-resources) - Get the information about resource if resource\_id is provided\. Fetch the list of resources match with the key\-value filter +[delete resource](#action-delete-resource) - Delete a resource +[create resource](#action-create-resource) - Create a resource +[update resource](#action-update-resource) - Update a resource +[apply policy](#action-apply-policy) - Apply policy on selected Ip address or MAC address +[clear policy](#action-clear-policy) - Clear policy on selected Ip address or MAC address + +## action: 'test connectivity' +Validate the asset configuration for connectivity\. This action logs into the device using a REST API call to check the connection and credentials + +Type: **test** +Read only: **True** + +#### Action Parameters +No parameters are required for this action + +#### Action Output +No Output + +## action: 'list endpoints' +List the endpoints configured on the system + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**mac\_address** | optional | Mac Address to filter on \(6 bytes, colon separated\) | string | `mac address` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.mac\_address | string | `mac address` +action\_result\.data\.\*\.SearchResult\.resources\.\*\.id | string | `ise endpoint id` `ise resource id` +action\_result\.data\.\*\.SearchResult\.resources\.\*\.link\.href | string | `url` +action\_result\.data\.\*\.SearchResult\.resources\.\*\.link\.rel | string | +action\_result\.data\.\*\.SearchResult\.resources\.\*\.link\.type | string | +action\_result\.data\.\*\.SearchResult\.resources\.\*\.name | string | +action\_result\.data\.\*\.SearchResult\.total | numeric | +action\_result\.summary\.Endpoints found | string | +action\_result\.summary\.endpoints\_found | numeric | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'get device info' +Get information about a specific endpoint + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**endpoint\_id** | required | ISE Endpoint ID for device | string | `ise endpoint id` `ise resource id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.endpoint\_id | string | `ise endpoint id` `ise resource id` +action\_result\.data\.\*\.ERSEndPoint\.customAttributes\.customAttributes\.ITSecurityBlock | string | +action\_result\.data\.\*\.ERSEndPoint\.description | string | +action\_result\.data\.\*\.ERSEndPoint\.groupId | string | +action\_result\.data\.\*\.ERSEndPoint\.id | string | `ise endpoint id` `ise resource id` +action\_result\.data\.\*\.ERSEndPoint\.identityStore | string | +action\_result\.data\.\*\.ERSEndPoint\.identityStoreId | string | +action\_result\.data\.\*\.ERSEndPoint\.link\.href | string | `url` +action\_result\.data\.\*\.ERSEndPoint\.link\.rel | string | +action\_result\.data\.\*\.ERSEndPoint\.link\.type | string | +action\_result\.data\.\*\.ERSEndPoint\.mac | string | +action\_result\.data\.\*\.ERSEndPoint\.name | string | +action\_result\.data\.\*\.ERSEndPoint\.portalUser | string | +action\_result\.data\.\*\.ERSEndPoint\.profileId | string | +action\_result\.data\.\*\.ERSEndPoint\.staticGroupAssignment | boolean | +action\_result\.data\.\*\.ERSEndPoint\.staticProfileAssignment | boolean | +action\_result\.summary | string | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'update device info' +Update information or attributes for a specific endpoint + +Type: **contain** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**endpoint\_id** | required | ISE Endpoint ID for device | string | `ise endpoint id` `ise resource id` +**attribute** | optional | Attribute to update for the Endpoint | string | +**attribute\_value** | optional | Value to put in the attribute for the Endpoint | string | +**custom\_attribute** | optional | Custom attribute to update for the Endpoint | string | +**custom\_attribute\_value** | optional | Value to put in the custom attribute for the Endpoint | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.attribute | string | +action\_result\.parameter\.attribute\_value | string | +action\_result\.parameter\.custom\_attribute | string | +action\_result\.parameter\.custom\_attribute\_value | string | +action\_result\.parameter\.endpoint\_id | string | `ise endpoint id` `ise resource id` +action\_result\.data\.\*\.UpdatedFieldsList\.updatedField\.\*\.field | string | +action\_result\.data\.\*\.UpdatedFieldsList\.updatedField\.\*\.newValue | string | +action\_result\.data\.\*\.UpdatedFieldsList\.updatedField\.\*\.oldValue | string | +action\_result\.summary | string | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'list sessions' +List the sessions currently available on the Monitoring node + +Type: **investigate** +Read only: **True** + +#### Action Parameters +No parameters are required for this action + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.data\.\*\.acct\_session\_id | string | +action\_result\.data\.\*\.audit\_session\_id | string | `ise session id` +action\_result\.data\.\*\.calling\_station\_id | string | `mac address` +action\_result\.data\.\*\.framed\_ip\_address | string | `ip` +action\_result\.data\.\*\.framed\_ipv6\_address | string | +action\_result\.data\.\*\.is\_quarantined | string | +action\_result\.data\.\*\.nas\_ip\_address | string | `nas server` +action\_result\.data\.\*\.server | string | `ise server` +action\_result\.data\.\*\.user\_name | string | `user name` +action\_result\.summary | string | +action\_result\.summary\.sessions\_found | numeric | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'quarantine device' +Quarantine the device + +Type: **contain** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**ip\_macaddress** | required | MAC or IP address of device to quarantine | string | `mac address` `ip` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.ip\_macaddress | string | `mac address` `ip` +action\_result\.data | string | +action\_result\.data\.\*\.EPS\_RESULT\.errorCode | string | +action\_result\.data\.\*\.EPS\_RESULT\.operationID | string | +action\_result\.data\.\*\.EPS\_RESULT\.requestID | string | +action\_result\.data\.\*\.EPS\_RESULT\.status | string | +action\_result\.summary | string | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'unquarantine device' +Unquarantine the device + +Type: **correct** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**ip\_macaddress** | required | MAC or IP address of device to unquarantine | string | `mac address` `ip` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.ip\_macaddress | string | `mac address` `ip` +action\_result\.data | string | +action\_result\.data\.\*\.EPS\_RESULT\.errorCode | string | +action\_result\.data\.\*\.EPS\_RESULT\.operationID | string | +action\_result\.data\.\*\.EPS\_RESULT\.requestID | string | +action\_result\.data\.\*\.EPS\_RESULT\.status | string | +action\_result\.summary | string | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'terminate session' +Terminate sessions + +Type: **contain** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**macaddress** | required | MAC address of device to terminate sessions of | string | `mac address` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.macaddress | string | `mac address` +action\_result\.data | string | +action\_result\.summary | string | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'list resources' +Lists all the resources configured on the system of a particular resource + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**resource** | required | Resource type of the resources to fetch | string | +**max\_results** | optional | Total number of observables to return | numeric | + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.max\_results | numeric | +action\_result\.parameter\.resource | string | +action\_result\.data\.\*\.description | string | +action\_result\.data\.\*\.id | string | `ise resource id` +action\_result\.data\.\*\.link\.href | string | `url` +action\_result\.data\.\*\.link\.rel | string | +action\_result\.data\.\*\.link\.type | string | +action\_result\.data\.\*\.name | string | +action\_result\.summary\.resources\_returned | numeric | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'get resources' +Get the information about resource if resource\_id is provided\. Fetch the list of resources match with the key\-value filter + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**resource** | required | Resource type of the resource to fetch | string | +**resource\_id** | optional | Resource ID | string | `ise resource id` +**key** | optional | Key | string | +**value** | optional | Value | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.key | string | +action\_result\.parameter\.resource | string | +action\_result\.parameter\.resource\_id | string | `ise resource id` +action\_result\.parameter\.value | string | +action\_result\.data\.\*\.description | string | +action\_result\.data\.\*\.groupId | string | +action\_result\.data\.\*\.id | string | `ise resource id` +action\_result\.data\.\*\.identityStore | string | +action\_result\.data\.\*\.identityStoreId | string | +action\_result\.data\.\*\.link\.href | string | `url` +action\_result\.data\.\*\.link\.rel | string | +action\_result\.data\.\*\.link\.type | string | +action\_result\.data\.\*\.mac | string | +action\_result\.data\.\*\.name | string | +action\_result\.data\.\*\.portalUser | string | +action\_result\.data\.\*\.profileId | string | +action\_result\.data\.\*\.staticGroupAssignment | boolean | +action\_result\.data\.\*\.staticProfileAssignment | boolean | +action\_result\.summary\.resource\_id | string | `ise resource id` +action\_result\.summary\.resources\_returned | numeric | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'delete resource' +Delete a resource + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**resource** | required | Resource type of the resource to be deleted | string | +**resource\_id** | required | Resource ID | string | `ise resource id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.resource | string | +action\_result\.parameter\.resource\_id | string | `ise resource id` +action\_result\.data | string | +action\_result\.summary | string | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'create resource' +Create a resource + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**resource** | required | Resource type of the resource to be created | string | +**resource\_json** | required | JSON which contains all values needed to create a resource | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.resource | string | +action\_result\.parameter\.resource\_json | string | +action\_result\.data | string | +action\_result\.summary | string | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'update resource' +Update a resource + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**resource** | required | Resource type of the resource to be created | string | +**resource\_id** | required | ID of resource | string | `ise resource id` +**key** | required | Key of resource which needs to be updated | string | +**value** | required | New value of key | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.status | string | +action\_result\.parameter\.key | string | +action\_result\.parameter\.resource | string | +action\_result\.parameter\.resource\_id | string | `ise resource id` +action\_result\.parameter\.value | string | +action\_result\.data | string | +action\_result\.summary | string | +action\_result\.message | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'apply policy' +Apply policy on selected Ip address or MAC address + +Type: **investigate** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**policy\_name** | required | Policy Name | string | +**ip\_mac\_address** | required | MAC or IP Address of the device | string | `mac address` `ip` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.ip\_mac\_address | string | `mac address` `ip` +action\_result\.parameter\.policy\_name | string | +action\_result\.message | string | +action\_result\.data | string | +action\_result\.summary | string | +action\_result\.status | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'clear policy' +Clear policy on selected Ip address or MAC address + +Type: **investigate** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**policy\_name** | required | Policy Name | string | +**ip\_mac\_address** | required | MAC or IP Address of the device | string | `mac address` `ip` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.ip\_mac\_address | string | `mac address` `ip` +action\_result\.parameter\.policy\_name | string | +action\_result\.message | string | +action\_result\.data | string | +action\_result\.summary | string | +action\_result\.status | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | \ No newline at end of file From fe201f6143018142fc648bd17ab688d375be19ef Mon Sep 17 00:00:00 2001 From: root Date: Fri, 7 Jan 2022 12:54:20 -0800 Subject: [PATCH 02/21] Bumped up the version of ciscoise from 2.0.5 to 2.0.6 --- ciscoise.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ciscoise.json b/ciscoise.json index dfb5f44..88bf693 100644 --- a/ciscoise.json +++ b/ciscoise.json @@ -5,8 +5,8 @@ "publisher": "Splunk", "type": "network security", "main_module": "ciscoise_connector.py", - "app_version": "2.0.5", - "utctime_updated": "2021-12-20T10:42:25.000000Z", + "app_version": "2.0.6", + "utctime_updated": "2022-01-07T20:54:18.000000Z", "package_name": "phantom_ciscoise", "product_vendor": "Cisco Systems", "product_name": "Cisco ISE", From a98d352217d40eafdc162625468f3ab0475aab97 Mon Sep 17 00:00:00 2001 From: splunk-soar-connectors-admin Date: Tue, 1 Feb 2022 12:56:29 -0800 Subject: [PATCH 03/21] Update pre-commit hook version --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f4494f..a55e12b 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.5 + rev: v1.9 hooks: - id: org-hook - id: package-app-dependencies From c00140b1172bd73a1d30515d097bd5fb3c653d64 Mon Sep 17 00:00:00 2001 From: splunk-soar-connectors-admin Date: Fri, 4 Feb 2022 13:23:43 -0800 Subject: [PATCH 04/21] Updating start-release action trigger --- .github/workflows/start-release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/start-release.yml b/.github/workflows/start-release.yml index 7d47230..7bbce79 100644 --- a/.github/workflows/start-release.yml +++ b/.github/workflows/start-release.yml @@ -1,5 +1,9 @@ name: Start Release -on: workflow_dispatch +on: + workflow_dispatch: + push: + tags: + - '*-beta*' jobs: start-release: runs-on: ubuntu-latest From a84cf703c36d188ed6190925c0e352845c00ed03 Mon Sep 17 00:00:00 2001 From: pdesai-crest Date: Thu, 10 Feb 2022 15:35:02 +0530 Subject: [PATCH 05/21] policy actions add --- .pre-commit-config.yaml | 2 +- ciscoise.json | 403 ++++++++++++++++-- ciscoise_connector.py | 106 ++++- ciscoise_consts.py | 1 + tox.ini | 7 + .../{ => py3}/Cerberus-1.3.4-py3-none-any.whl | Bin .../setuptools-59.6.0-py3-none-any.whl | Bin wheels/py3/setuptools-60.8.2-py3-none-any.whl | Bin 0 -> 1060226 bytes .../certifi-2021.10.8-py2.py3-none-any.whl | Bin .../chardet-3.0.4-py2.py3-none-any.whl | Bin .../idna-2.10-py2.py3-none-any.whl | Bin .../requests-2.25.0-py2.py3-none-any.whl | Bin .../urllib3-1.26.8-py2.py3-none-any.whl} | Bin 138764 -> 138699 bytes .../xmltodict-0.12.0-py2.py3-none-any.whl | Bin 14 files changed, 485 insertions(+), 34 deletions(-) create mode 100644 tox.ini rename wheels/{ => py3}/Cerberus-1.3.4-py3-none-any.whl (100%) rename wheels/{ => py3}/setuptools-59.6.0-py3-none-any.whl (100%) create mode 100644 wheels/py3/setuptools-60.8.2-py3-none-any.whl rename wheels/{ => shared}/certifi-2021.10.8-py2.py3-none-any.whl (100%) rename wheels/{ => shared}/chardet-3.0.4-py2.py3-none-any.whl (100%) rename wheels/{ => shared}/idna-2.10-py2.py3-none-any.whl (100%) rename wheels/{ => shared}/requests-2.25.0-py2.py3-none-any.whl (100%) rename wheels/{urllib3-1.26.7-py2.py3-none-any.whl => shared/urllib3-1.26.8-py2.py3-none-any.whl} (57%) rename wheels/{ => shared}/xmltodict-0.12.0-py2.py3-none-any.whl (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a55e12b..fa97c3c 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.9 + rev: v1.11 hooks: - id: org-hook - id: package-app-dependencies diff --git a/ciscoise.json b/ciscoise.json index 88bf693..fdc5e84 100644 --- a/ciscoise.json +++ b/ciscoise.json @@ -5,16 +5,16 @@ "publisher": "Splunk", "type": "network security", "main_module": "ciscoise_connector.py", - "app_version": "2.0.6", + "app_version": "3.0.0", "utctime_updated": "2022-01-07T20:54:18.000000Z", "package_name": "phantom_ciscoise", "product_vendor": "Cisco Systems", "product_name": "Cisco ISE", "product_version_regex": "/([2].[67])|([3].[01])/", - "min_phantom_version": "5.0.0", + "min_phantom_version": "5.1.0", "logo": "logo_cisco.svg", "logo_dark": "logo_cisco_dark.svg", - "license": "Copyright (c) 2014-2021 Splunk Inc.", + "license": "Copyright (c) 2014-2022 Splunk Inc.", "python_version": "3", "fips_compliant": true, "latest_tested_version": [ @@ -1110,7 +1110,8 @@ "order": 1, "contains": [ "ise resource id" - ] + ], + "primary": true }, "key": { "description": "Key", @@ -1343,7 +1344,8 @@ "order": 1, "contains": [ "ise resource id" - ] + ], + "primary": true } }, "output": [ @@ -1538,7 +1540,8 @@ "order": 1, "contains": [ "ise resource id" - ] + ], + "primary": true }, "key": { "description": "Key of resource which needs to be updated", @@ -1648,7 +1651,6 @@ "description": "Policy Name", "data_type": "string", "order": 0, - "primary": false, "required": true }, "ip_mac_address": { @@ -1670,6 +1672,14 @@ "title": "Endpoints" }, "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "success", + "failed" + ] + }, { "data_path": "action_result.parameter.ip_mac_address", "data_type": "string", @@ -1680,7 +1690,7 @@ "example_values": [ "11:11:11:11:11:11" ], - "column_name": "IP MAC address", + "column_name": "IP MAC Address", "column_order": 0 }, { @@ -1692,15 +1702,6 @@ ], "column_order": 1 }, - { - "data_path": "action_result.message", - "data_type": "string", - "example_values": [ - "Policy applied" - ], - "column_name": "Status", - "column_order": 2 - }, { "data_path": "action_result.data", "data_type": "string" @@ -1710,12 +1711,13 @@ "data_type": "string" }, { - "data_path": "action_result.status", + "data_path": "action_result.message", "data_type": "string", "example_values": [ - "success", - "failed" - ] + "Policy applied" + ], + "column_name": "Status", + "column_order": 2 }, { "data_path": "summary.total_objects", @@ -1766,6 +1768,14 @@ "title": "Endpoints" }, "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "success", + "failed" + ] + }, { "data_path": "action_result.parameter.ip_mac_address", "data_type": "string", @@ -1776,7 +1786,7 @@ "example_values": [ "11:11:11:11:11:11" ], - "column_name": "IP MAC address", + "column_name": "IP MAC Address", "column_order": 0 }, { @@ -1788,6 +1798,14 @@ "column_name": "Policy Name", "column_order": 1 }, + { + "data_path": "action_result.data", + "data_type": "string" + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, { "data_path": "action_result.message", "data_type": "string", @@ -1797,6 +1815,218 @@ "column_name": "Status", "column_order": 2 }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "versions": "EQ(*)" + }, + { + "action": "list policies", + "description": "Lists all the policies available", + "type": "investigate", + "identifier": "list_policies", + "read_only": true, + "parameters": {}, + "render": { + "type": "table", + "width": 12, + "height": 5, + "title": "Policies" + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "success", + "failed" + ] + }, + { + "data_path": "action_result.data.*.actions", + "data_type": "string", + "example_value": "SHUTDOWN", + "column_name": "Action Type", + "column_order": 1 + }, + { + "data_path": "action_result.data.*.id", + "data_type": "string", + "example_values": [ + "policy_name" + ], + "column_name": "Policy ID", + "column_order": 0, + "contains": [ + "ise policy id" + ] + }, + { + "data_path": "action_result.data.*.link.href", + "data_type": "string", + "example_values": [ + "https://10.0.0.0:9060/ers/config/ancpolicy/policy_name" + ] + }, + { + "data_path": "action_result.data.*.link.rel", + "data_type": "string", + "example_values": [ + "self" + ] + }, + { + "data_path": "action_result.data.*.link.type", + "data_type": "string", + "example_values": [ + "application/json" + ] + }, + { + "data_path": "action_result.data.*.name", + "data_type": "string", + "example_values": [ + "policy_name" + ] + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "action_result.summary.policies_found", + "data_type": "numeric", + "example_value": 6 + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Policies found: 1" + ] + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "versions": "EQ(*)" + }, + { + "action": "create policy", + "description": "Create Policy", + "type": "investigate", + "identifier": "create_policy", + "read_only": true, + "parameters": { + "name": { + "data_type": "string", + "description": "Policy Name", + "order": 0, + "default": false, + "required": true + }, + "ph": { + "data_type": "ph", + "order": 1 + }, + "port_bounce": { + "data_type": "boolean", + "description": "PORT_BOUNCE action type", + "order": 2, + "default": false + }, + "shutdown": { + "data_type": "boolean", + "description": "SHUTDOWN action type", + "order": 3, + "default": false + }, + "re_authenticate": { + "data_type": "boolean", + "description": "RE_AUTHENTICATE action type", + "order": 4, + "default": false + }, + "quarantine": { + "data_type": "boolean", + "description": "QUARANTINE action type", + "order": 5, + "default": true + } + }, + "render": { + "type": "table", + "width": 12, + "height": 5, + "title": "Results" + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "success", + "failed" + ] + }, + { + "data_path": "action_result.parameter.name", + "data_type": "string", + "example_values": [ + "policy_name" + ] + }, + { + "data_path": "action_result.parameter.port_bounce", + "data_type": "boolean", + "example_values": [ + true + ] + }, + { + "data_path": "action_result.parameter.quarantine", + "data_type": "boolean", + "example_values": [ + false + ] + }, + { + "data_path": "action_result.parameter.re_authenticate", + "data_type": "boolean", + "example_values": [ + false + ] + }, + { + "data_path": "action_result.parameter.shutdown", + "data_type": "boolean", + "example_values": [ + false + ] + }, { "data_path": "action_result.data", "data_type": "string" @@ -1805,6 +2035,57 @@ "data_path": "action_result.summary", "data_type": "string" }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Policy created" + ], + "column_name": "Status", + "column_order": 0 + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "versions": "EQ(*)" + }, + { + "action": "delete policy", + "description": "Delete a policy", + "type": "investigate", + "identifier": "delete_policy", + "read_only": true, + "parameters": { + "policy_id": { + "description": "Policy ID", + "data_type": "string", + "order": 0, + "required": true, + "contains": [ + "ise policy id" + ], + "primary": true + } + }, + "render": { + "type": "table", + "width": 12, + "height": 5, + "title": "Results" + }, + "output": [ { "data_path": "action_result.status", "data_type": "string", @@ -1813,6 +2094,30 @@ "failed" ] }, + { + "data_path": "action_result.parameter.policy_id", + "data_type": "string", + "contains": [ + "ise policy id" + ] + }, + { + "data_path": "action_result.data", + "data_type": "string" + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Policy deleted" + ], + "column_name": "Status", + "column_order": 0 + }, { "data_path": "summary.total_objects", "data_type": "numeric", @@ -1835,36 +2140,72 @@ "wheel": [ { "module": "Cerberus", - "input_file": "wheels/Cerberus-1.3.4-py3-none-any.whl" + "input_file": "wheels/py3/Cerberus-1.3.4-py3-none-any.whl" + }, + { + "module": "certifi", + "input_file": "wheels/shared/certifi-2021.10.8-py2.py3-none-any.whl" + }, + { + "module": "chardet", + "input_file": "wheels/shared/chardet-3.0.4-py2.py3-none-any.whl" + }, + { + "module": "idna", + "input_file": "wheels/shared/idna-2.10-py2.py3-none-any.whl" + }, + { + "module": "requests", + "input_file": "wheels/shared/requests-2.25.0-py2.py3-none-any.whl" + }, + { + "module": "setuptools", + "input_file": "wheels/py3/setuptools-59.6.0-py3-none-any.whl" + }, + { + "module": "urllib3", + "input_file": "wheels/shared/urllib3-1.26.8-py2.py3-none-any.whl" + }, + { + "module": "xmltodict", + "input_file": "wheels/shared/xmltodict-0.12.0-py2.py3-none-any.whl" + } + ] + }, + "pip39_dependencies": { + "wheel": [ + { + "module": "Cerberus", + "input_file": "wheels/py3/Cerberus-1.3.4-py3-none-any.whl" }, { "module": "certifi", - "input_file": "wheels/certifi-2021.10.8-py2.py3-none-any.whl" + "input_file": "wheels/shared/certifi-2021.10.8-py2.py3-none-any.whl" }, { "module": "chardet", - "input_file": "wheels/chardet-3.0.4-py2.py3-none-any.whl" + "input_file": "wheels/shared/chardet-3.0.4-py2.py3-none-any.whl" }, { "module": "idna", - "input_file": "wheels/idna-2.10-py2.py3-none-any.whl" + "input_file": "wheels/shared/idna-2.10-py2.py3-none-any.whl" }, { "module": "requests", - "input_file": "wheels/requests-2.25.0-py2.py3-none-any.whl" + "input_file": "wheels/shared/requests-2.25.0-py2.py3-none-any.whl" }, { "module": "setuptools", - "input_file": "wheels/setuptools-59.6.0-py3-none-any.whl" + "input_file": "wheels/py3/setuptools-60.8.2-py3-none-any.whl" }, { "module": "urllib3", - "input_file": "wheels/urllib3-1.26.7-py2.py3-none-any.whl" + "input_file": "wheels/shared/urllib3-1.26.8-py2.py3-none-any.whl" }, { "module": "xmltodict", - "input_file": "wheels/xmltodict-0.12.0-py2.py3-none-any.whl" + "input_file": "wheels/shared/xmltodict-0.12.0-py2.py3-none-any.whl" } ] } -} \ No newline at end of file +} diff --git a/ciscoise_connector.py b/ciscoise_connector.py index 2e86278..5cc3090 100644 --- a/ciscoise_connector.py +++ b/ciscoise_connector.py @@ -30,7 +30,6 @@ class CiscoISEConnector(BaseConnector): - # actions supported by this script ACTION_ID_LIST_SESSIONS = "list_sessions" ACTION_ID_TERMINATE_SESSION = "terminate_session" @@ -47,6 +46,9 @@ class CiscoISEConnector(BaseConnector): ACTION_ID_UPDATE_RESOURCE = "update_resource" ACTION_ID_APPLY_POLICY = "apply_policy" ACTION_ID_CLEAR_POLICY = "clear_policy" + ACTION_ID_LIST_POLICIES = "list_policies" + ACTION_ID_CREATE_POLICY = "create_policy" + ACTION_ID_DELETE_POLICY = "delete_policy" def __init__(self): @@ -107,7 +109,13 @@ def _call_ers_api(self, endpoint, action_result, data=None, allow_unknown=True, return action_result.set_status(phantom.APP_ERROR, CISCOISE_ERR_REST_API, e), ret_data try: headers = {"Content-Type": "application/json", "ACCEPT": "application/json"} - resp = request_func(url, json=data, verify=verify, headers=headers, auth=self._ers_auth) + resp = request_func( # nosemgrep: python.requests.best-practice.use-timeout.use-timeout + url, + json=data, + verify=verify, + headers=headers, + auth=self._ers_auth + ) except Exception as e: return action_result.set_status(phantom.APP_ERROR, CISCOISE_ERR_REST_API, e), ret_data @@ -727,6 +735,94 @@ def _clear_policy(self, param): action_result.add_data(ret_data) return action_result.set_status(phantom.APP_SUCCESS, "Policy cleared") + def _list_policies(self, param): + + ret_val = phantom.APP_SUCCESS + + action_result = self.add_action_result(ActionResult(dict(param))) + + ret_data = None + endpoint = ERS_POLICIES + + ret_val, ret_data = self._call_ers_api(endpoint, action_result) + + if phantom.is_fail(ret_val): + return action_result.get_status() + + total = ret_data["SearchResult"]["total"] + policies = ret_data["SearchResult"]["resources"] + + for policy in policies: + endpoint = f"{ERS_POLICIES}/{policy['id']}" + + ret_val, ret_data = self._call_ers_api(endpoint, action_result) + if phantom.is_fail(ret_val): + return action_result.get_status() + data = ret_data["ErsAncPolicy"] + data['actions'] = ', '.join(data['actions']) + action_result.add_data(data) + + action_result.update_summary({"policies_found": total}) + + return action_result.set_status(phantom.APP_SUCCESS) + + def _delete_policy(self, param): + + ret_val = phantom.APP_SUCCESS + + action_result = self.add_action_result(ActionResult(dict(param))) + + ret_data = None + endpoint = f"{ERS_POLICIES}/{param['policy_id']}" + + ret_val, ret_data = self._call_ers_api(endpoint, action_result, method="delete") + + if phantom.is_fail(ret_val): + return action_result.get_status() + + return action_result.set_status(phantom.APP_SUCCESS, "Policy deleted") + + def _create_policy(self, param): + + ret_val = phantom.APP_SUCCESS + + action_result = self.add_action_result(ActionResult(dict(param))) + + name = param["name"] + quarantine = param.get("quarantine", False) + port_bounce = param.get("port_bounce", False) + re_authenticate = param.get("re_authenticate", False) + shutdown = param.get("shutdown", False) + + if not (quarantine or port_bounce or re_authenticate or shutdown): + return action_result.set_status(phantom.APP_ERROR, "Atleast one action type is required") + + body = { + "ErsAncPolicy": { + "name": name, + "actions": [] + } + } + + if quarantine: + body["ErsAncPolicy"]["actions"].append("QUARANTINE") + if port_bounce: + body["ErsAncPolicy"]["actions"].append("PORTBOUNCE") + if re_authenticate: + body["ErsAncPolicy"]["actions"].append("RE_AUTHENTICATE") + if shutdown: + body["ErsAncPolicy"]["actions"].append("SHUTDOWN") + + ret_data = None + endpoint = f"{ERS_POLICIES}" + + ret_val, ret_data = self._call_ers_api(endpoint, action_result, method="post", data=body) + + if phantom.is_fail(ret_val): + return action_result.get_status() + + return action_result.set_status(phantom.APP_SUCCESS, 'Policy created') + def _test_connectivity_to_device(self, base_url, verify=True): try: rest_endpoint = "{0}{1}".format(base_url, ACTIVE_LIST_REST) @@ -798,6 +894,12 @@ def handle_action(self, param): result = self._apply_policy(param) elif action == self.ACTION_ID_CLEAR_POLICY: result = self._clear_policy(param) + elif action == self.ACTION_ID_LIST_POLICIES: + result = self._list_policies(param) + elif action == self.ACTION_ID_CREATE_POLICY: + result = self._create_policy(param) + elif action == self.ACTION_ID_DELETE_POLICY: + result = self._delete_policy(param) return result diff --git a/ciscoise_consts.py b/ciscoise_consts.py index d141e81..afdc1ec 100644 --- a/ciscoise_consts.py +++ b/ciscoise_consts.py @@ -34,6 +34,7 @@ ERS_RESOURCE_REST = ":9060/ers/config/{resource}" ERS_ENDPOINT_ANC_APPLY = ":9060/ers/config/ancendpoint/apply" ERS_ENDPOINT_ANC_CLEAR = ":9060/ers/config/ancendpoint/clear" +ERS_POLICIES = ":9060/ers/config/ancpolicy" # Error/Success CISCOISE_ERR_TEST_CONNECTIVITY_FAILED = "Test connectivity failed" diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..127a08b --- /dev/null +++ b/tox.ini @@ -0,0 +1,7 @@ +[flake8] +max-line-length = 145 +max-complexity = 28 +ignore = F403,E128,E126,E111,E121,E127,E731,E201,E202,F405,E722,D,W292 + +[isort] +line_length = 145 diff --git a/wheels/Cerberus-1.3.4-py3-none-any.whl b/wheels/py3/Cerberus-1.3.4-py3-none-any.whl similarity index 100% rename from wheels/Cerberus-1.3.4-py3-none-any.whl rename to wheels/py3/Cerberus-1.3.4-py3-none-any.whl diff --git a/wheels/setuptools-59.6.0-py3-none-any.whl b/wheels/py3/setuptools-59.6.0-py3-none-any.whl similarity index 100% rename from wheels/setuptools-59.6.0-py3-none-any.whl rename to wheels/py3/setuptools-59.6.0-py3-none-any.whl diff --git a/wheels/py3/setuptools-60.8.2-py3-none-any.whl b/wheels/py3/setuptools-60.8.2-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..a64ea495410d288540b80b0d76dec76d1b75ea9d GIT binary patch literal 1060226 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