From a39ca2a231612bf606435a6edd86c1aa4a681217 Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 14:15:05 -0600 Subject: [PATCH 01/21] PAPP-35033 Update logos and other branding --- ciscotalosintelligence.svg | 1 + ciscotalosintelligence_dark.svg | 1 + talosintelligence.json | 14 +- talosintelligence.svg | 199 ------------------------- talosintelligence_dark.svg | 253 -------------------------------- 5 files changed, 7 insertions(+), 461 deletions(-) create mode 100644 ciscotalosintelligence.svg create mode 100644 ciscotalosintelligence_dark.svg delete mode 100644 talosintelligence.svg delete mode 100644 talosintelligence_dark.svg diff --git a/ciscotalosintelligence.svg b/ciscotalosintelligence.svg new file mode 100644 index 0000000..ec1a7a0 --- /dev/null +++ b/ciscotalosintelligence.svg @@ -0,0 +1 @@ + diff --git a/ciscotalosintelligence_dark.svg b/ciscotalosintelligence_dark.svg new file mode 100644 index 0000000..4fd6494 --- /dev/null +++ b/ciscotalosintelligence_dark.svg @@ -0,0 +1 @@ + diff --git a/talosintelligence.json b/talosintelligence.json index 2074a3e..64fb77a 100644 --- a/talosintelligence.json +++ b/talosintelligence.json @@ -1,11 +1,11 @@ { "appid": "7c653487-22c8-4ec1-bca0-16a8b1513c86", - "name": "Talos Intelligence V2", - "description": "This app provides investigative actions for Talos Intelligence", + "name": "Cisco Talos Intelligence", + "description": "This app provides investigative actions for Talos Cloud Intelligence", "type": "information", "product_vendor": "Cisco", - "logo": "talosintelligence.svg", - "logo_dark": "talosintelligence_dark.svg", + "logo": "ciscotalosintelligence.svg", + "logo_dark": "ciscotalosintelligence_dark.svg", "product_name": "Talos Cloud Intelligence", "python_version": "3", "latest_tested_versions": [ @@ -16,7 +16,7 @@ "license": "Copyright (c) 2024 Splunk Inc.", "app_version": "1.0.1", "utctime_updated": "2024-06-21T18:40:03.685771Z", - "package_name": "phantom_talosintelligencev2", + "package_name": "phantom_ciscotalosintelligence", "fips_compliant": false, "main_module": "talosintelligence_connector.py", "min_phantom_version": "6.2.1.305", @@ -331,10 +331,6 @@ "module": "hyperframe", "input_file": "wheels/py3/hyperframe-6.0.1-py3-none-any.whl" }, - { - "module": "pydantic", - "input_file": "wheels/py39/pydantic-1.10.13-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" - }, { "module": "sniffio", "input_file": "wheels/py3/sniffio-1.3.1-py3-none-any.whl" diff --git a/talosintelligence.svg b/talosintelligence.svg deleted file mode 100644 index 0f8a266..0000000 --- a/talosintelligence.svg +++ /dev/null @@ -1,199 +0,0 @@ - - - - diff --git a/talosintelligence_dark.svg b/talosintelligence_dark.svg deleted file mode 100644 index e742dc0..0000000 --- a/talosintelligence_dark.svg +++ /dev/null @@ -1,253 +0,0 @@ - - - - From e7370205d27d4c467f72a521b18d4262199a0bae Mon Sep 17 00:00:00 2001 From: splunk-soar-connectors-admin Date: Wed, 30 Oct 2024 20:15:57 +0000 Subject: [PATCH 02/21] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e4b29cc..76e11fc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [comment]: # "Auto-generated SOAR connector documentation" -# Talos Intelligence V2 +# Cisco Talos Intelligence Publisher: Splunk Connector Version: 1.0.1 @@ -8,7 +8,7 @@ Product Name: Talos Cloud Intelligence Product Version Supported (regex): ".\*" Minimum Product Version: 6.2.1.305 -This app provides investigative actions for Talos Intelligence +This app provides investigative actions for Talos Cloud Intelligence [comment]: # " File: README.md" [comment]: # "Copyright (c) 2024 Splunk Inc." From 9988158df985990447df2b530b65aa5f577fd806 Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 14:21:26 -0600 Subject: [PATCH 03/21] add wheel back --- talosintelligence.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/talosintelligence.json b/talosintelligence.json index 64fb77a..3b8d53c 100644 --- a/talosintelligence.json +++ b/talosintelligence.json @@ -331,6 +331,10 @@ "module": "hyperframe", "input_file": "wheels/py3/hyperframe-6.0.1-py3-none-any.whl" }, + { + "module": "pydantic", + "input_file": "wheels/py39/pydantic-1.10.13-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" + }, { "module": "sniffio", "input_file": "wheels/py3/sniffio-1.3.1-py3-none-any.whl" From 42b2b5bed30d1a3d835583123e704809c369656e Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 14:27:56 -0600 Subject: [PATCH 04/21] lint --- .pre-commit-config.yaml | 4 +- pyproject.toml | 8 ++++ talosintelligence_connector.py | 69 +++++++++------------------------- tox.ini | 5 +-- 4 files changed, 28 insertions(+), 58 deletions(-) create mode 100644 pyproject.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5e8fb83..9f55448 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,11 @@ repos: - repo: https://github.com/phantomcyber/dev-cicd-tools - rev: v1.17 + rev: v1.23 hooks: - id: org-hook - id: package-app-dependencies - repo: https://github.com/Yelp/detect-secrets - rev: v1.4.0 + rev: v1.5.0 hooks: - id: detect-secrets args: ['--no-verify', '--exclude-files', '^gsgmail.json$'] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4c594fc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[tool.black] +line-length = 145 +target-version = ['py39'] +verbose = true + +[tool.isort] +line_length = 145 +profile = "black" diff --git a/talosintelligence_connector.py b/talosintelligence_connector.py index e079600..d2f1216 100644 --- a/talosintelligence_connector.py +++ b/talosintelligence_connector.py @@ -21,6 +21,7 @@ import tempfile import httpx + # Phantom App imports import phantom.app as phantom import requests @@ -54,9 +55,7 @@ def _process_empty_response(self, response, action_result): return RetVal(phantom.APP_SUCCESS, {}) return RetVal( - action_result.set_status( - phantom.APP_ERROR, "Empty response and no information in the header" - ), + action_result.set_status(phantom.APP_ERROR, "Empty response and no information in the header"), None, ) @@ -73,9 +72,7 @@ def _process_html_response(self, response, action_result): except: error_text = "Cannot parse error details" - message = "Status Code: {0}. Data from server:\n{1}\n".format( - status_code, error_text - ) + message = "Status Code: {0}. Data from server:\n{1}\n".format(status_code, error_text) message = message.replace("{", "{{").replace("}", "}}") return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) @@ -98,9 +95,7 @@ def _process_json_response(self, r, action_result): return RetVal(phantom.APP_SUCCESS, resp_json) # You should process the error returned in the json - message = "Error from server. Status Code: {0} Data from server: {1}".format( - r.status_code, r.text.replace("{", "{{").replace("}", "}}") - ) + message = "Error from server. Status Code: {0} Data from server: {1}".format(r.status_code, r.text.replace("{", "{{").replace("}", "}}")) return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) @@ -145,9 +140,7 @@ def _make_rest_call(self, endpoint, action_result, method="get", **kwargs): # Create a URL to connect to url = self._base_url + endpoint - with tempfile.NamedTemporaryFile( - mode="w+", delete=False, suffix="test" - ) as temp_file: + with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix="test") as temp_file: combined_file = ( "-----BEGIN CERTIFICATE-----\n" f"{self._certificate}\n" @@ -209,9 +202,7 @@ def _handle_test_connectivity(self, param): return action_result.set_status(phantom.APP_SUCCESS) def _handle_ip_reputation(self, param): - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) action_result = self.add_action_result(ActionResult(dict(param))) ip = param["ip"] @@ -221,9 +212,7 @@ def _handle_ip_reputation(self, param): big_endian = int(ip_addr) except Exception: - return action_result.set_status( - phantom.APP_ERROR, "Please provide a valid IP Address" - ) + return action_result.set_status(phantom.APP_ERROR, "Please provide a valid IP Address") payload = { "urls": {"endpoint": [{"ipv4_addr": big_endian}]}, @@ -235,9 +224,7 @@ def _handle_ip_reputation(self, param): return action_result.set_status(phantom.APP_SUCCESS) def _handle_domain_reputation(self, param): - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) action_result = self.add_action_result(ActionResult(dict(param))) domain = param["domain"] @@ -249,9 +236,7 @@ def _handle_domain_reputation(self, param): return action_result.set_status(phantom.APP_SUCCESS) def _handle_url_reputation(self, param): - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) action_result = self.add_action_result(ActionResult(dict(param))) url = param["url"] @@ -269,15 +254,11 @@ def _query_reputation(self, action_result, payload): return action_result.get_status() # make rest call - ret_val, response = self._make_rest_call( - ENDPOINT_QUERY_REPUTATION_V3, action_result, method="post", json=payload - ) + ret_val, response = self._make_rest_call(ENDPOINT_QUERY_REPUTATION_V3, action_result, method="post", json=payload) response_taxonomy_map_version = response["taxonomy_map_version"] if response_taxonomy_map_version > self._state["taxonomy_version"]: - taxonomy_ret_val, taxonomy = self._fetch_taxonomy( - action_result, allow_cache=False - ) + taxonomy_ret_val, taxonomy = self._fetch_taxonomy(action_result, allow_cache=False) if phantom.is_fail(ret_val) or "results" not in response: return action_result.get_status() @@ -298,12 +279,8 @@ def _query_reputation(self, action_result, payload): continue category = taxonomy["taxonomies"][tax_id]["name"]["en-us"]["text"] - name = taxonomy["taxonomies"][tax_id]["entries"][entry_id]["name"][ - "en-us" - ]["text"] - description = taxonomy["taxonomies"][tax_id]["entries"][entry_id][ - "description" - ]["en-us"]["text"] + name = taxonomy["taxonomies"][tax_id]["entries"][entry_id]["name"]["en-us"]["text"] + description = taxonomy["taxonomies"][tax_id]["entries"][entry_id]["description"]["en-us"]["text"] if category == "Threat Levels": threat_level = name @@ -316,18 +293,10 @@ def _query_reputation(self, action_result, payload): action_result.add_data({"Threat Level": threat_level}) summary["Threat Categories"] = threat_categories - action_result.add_data( - {"Threat Categories": ", ".join(list(threat_categories.keys()))} - ) + action_result.add_data({"Threat Categories": ", ".join(list(threat_categories.keys()))}) summary["Acceptable Use Policy Categories"] = aup_categories - action_result.add_data( - { - "Acceptable Use Policy Categories": ", ".join( - list(aup_categories.keys()) - ) - } - ) + action_result.add_data({"Acceptable Use Policy Categories": ", ".join(list(aup_categories.keys()))}) def _fetch_taxonomy(self, action_result, allow_cache=True): payload = {"app_info": self._appinfo} @@ -335,9 +304,7 @@ def _fetch_taxonomy(self, action_result, allow_cache=True): if "taxonomy" in self._state and allow_cache: return 1, self._state["taxonomy"] - ret_val, response = self._make_rest_call( - ENDPOINT_QUERY_TAXONOMIES, action_result, method="post", json=payload - ) + ret_val, response = self._make_rest_call(ENDPOINT_QUERY_TAXONOMIES, action_result, method="post", json=payload) taxonomy = response["catalogs"][str(self._catalog_id)] self._state = {"taxonomy": taxonomy, "taxonomy_version": response["version"]} @@ -374,9 +341,7 @@ def initialize(self): config = self.get_config() def insert_newlines(string, every=64): - return "\n".join( - string[i: i + every] for i in range(0, len(string), every) - ) + return "\n".join(string[i : i + every] for i in range(0, len(string), every)) self._base_url = config["base_url"] self._certificate = insert_newlines(config["certificate"]) diff --git a/tox.ini b/tox.ini index c4644ad..720a141 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,4 @@ [flake8] max-line-length = 145 max-complexity = 28 -extend-ignore = F403,E128,E126,E111,E121,E127,E731,E201,E202,F405,E722,D,W292 - -[isort] -line_length = 145 +extend-ignore = F403,E128,E126,E121,E127,E731,E201,E202,E203,E701,F405,E722,D,W503 From 15aab9c58baa381c7c2a36ddf898e8f4fefe1be3 Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 14:32:04 -0600 Subject: [PATCH 05/21] logo dimensions --- ciscotalosintelligence.svg | 2 +- ciscotalosintelligence_dark.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ciscotalosintelligence.svg b/ciscotalosintelligence.svg index ec1a7a0..34751d7 100644 --- a/ciscotalosintelligence.svg +++ b/ciscotalosintelligence.svg @@ -1 +1 @@ - + diff --git a/ciscotalosintelligence_dark.svg b/ciscotalosintelligence_dark.svg index 4fd6494..e3726a7 100644 --- a/ciscotalosintelligence_dark.svg +++ b/ciscotalosintelligence_dark.svg @@ -1 +1 @@ - + From 260e96e26a2fa35116420ebb96b8c36c1691493e Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Tue, 8 Oct 2024 14:24:22 -0600 Subject: [PATCH 06/21] PAPP-34786 rename app to ciscotalosintelligence --- ciscotalosintelligence.json | 338 +++++++++++++++++++++ ciscotalosintelligence_connector.py | 455 ++++++++++++++++++++++++++++ ciscotalosintelligence_consts.py | 6 + 3 files changed, 799 insertions(+) create mode 100644 ciscotalosintelligence.json create mode 100644 ciscotalosintelligence_connector.py create mode 100644 ciscotalosintelligence_consts.py diff --git a/ciscotalosintelligence.json b/ciscotalosintelligence.json new file mode 100644 index 0000000..dd00969 --- /dev/null +++ b/ciscotalosintelligence.json @@ -0,0 +1,338 @@ +{ + "appid": "7c653487-22c8-4ec1-bca0-16a8b1513c86", + "name": "Cisco Talos Intelligence", + "description": "This app provides investigative actions for Cisco Talos Intelligence", + "type": "information", + "product_vendor": "Cisco", + "logo": "ciscotalosintelligence.svg", + "logo_dark": "ciscotalosintelligence_dark.svg", + "product_name": "Talos Cloud Intelligence", + "python_version": "3", + "product_version_regex": ".*", + "publisher": "Splunk Community", + "license": "Copyright (c) Splunk Community, 2024", + "app_version": "1.0.1", + "utctime_updated": "2024-06-21T18:40:03.685771Z", + "package_name": "phantom_ciscotalosintelligence", + "main_module": "ciscotalosintelligence_connector.py", + "min_phantom_version": "6.2.1.305", + "app_wizard_version": "1.0.0", + "configuration": { + "base_url": { + "data_type": "string", + "order": 0, + "description": "Base URL provided by Talos", + "default": "consumerdev-api.talos.cisco.com", + "required": true, + "name": "base_url", + "id": 0 + }, + "certificate": { + "data_type": "password", + "order": 1, + "description": "Certificate contents to authenticate with Talos", + "name": "certificate", + "id": 1 + }, + "key": { + "data_type": "password", + "order": 2, + "description": "Private key to authenticate with Talos", + "name": "key", + "id": 2 + } + }, + "actions": [ + { + "action": "test connectivity", + "identifier": "test_connectivity", + "description": "Validate the asset configuration for connectivity using supplied configuration", + "verbose": "", + "type": "test", + "read_only": true, + "parameters": {}, + "output": [], + "versions": "EQ(*)" + }, + { + "action": "ip reputation", + "identifier": "ip_reputation", + "description": "Queries IP info", + "verbose": "", + "type": "investigate", + "read_only": true, + "parameters": { + "ip": { + "description": "IP to query", + "data_type": "string", + "required": true, + "primary": true, + "contains": [ + "ip" + ], + "value_list": [], + "default": "", + "order": 0, + "name": "ip" + } + }, + "output": [ + { + "data_path": "action_result.parameter.ip", + "data_type": "string", + "contains": [ + "ip" + ], + "column_name": "ip", + "column_order": 0 + }, + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "status", + "column_order": 1 + }, + { + "data_path": "action_result.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric" + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.0.Threat Level", + "data_type": "string", + "column_name": "threat level", + "column_order": 2 + }, + { + "data_path": "action_result.data.1.Threat Categories", + "data_type": "string", + "column_name": "threat categories", + "column_order": 3 + }, + { + "data_path": "action_result.data.2.Acceptable Use Policy Categories", + "data_type": "string", + "column_name": "Acceptable Use Policy Categories", + "column_order": 4 + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "domain reputation", + "identifier": "domain_reputation", + "description": "Queries domain info", + "verbose": "", + "type": "investigate", + "read_only": true, + "parameters": { + "domain": { + "description": "Domain to query", + "data_type": "string", + "required": true, + "primary": true, + "contains": [ + "domain", + "url" + ], + "value_list": [], + "default": "", + "order": 0, + "name": "domain" + } + }, + "output": [ + { + "data_path": "action_result.parameter.domain", + "data_type": "string", + "contains": [ + "domain", + "url" + ], + "column_name": "domain", + "column_order": 0 + }, + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "status", + "column_order": 1 + }, + { + "data_path": "action_result.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric" + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.0.Threat Level", + "data_type": "string", + "column_name": "threat level", + "column_order": 2 + }, + { + "data_path": "action_result.data.1.Threat Categories", + "data_type": "string", + "column_name": "threat categories", + "column_order": 3 + }, + { + "data_path": "action_result.data.2.Acceptable Use Policy Categories", + "data_type": "string", + "column_name": "Acceptable Use Policy Categories", + "column_order": 4 + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "url reputation", + "identifier": "url_reputation", + "description": "Queries URL info", + "verbose": "", + "type": "investigate", + "read_only": true, + "parameters": { + "url": { + "description": "URL to query", + "data_type": "string", + "required": true, + "primary": true, + "contains": [ + "url" + ], + "value_list": [], + "default": "", + "order": 0, + "name": "url" + } + }, + "output": [ + { + "data_path": "action_result.parameter.url", + "data_type": "string", + "contains": [ + "url" + ], + "column_name": "url", + "column_order": 0 + }, + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "status", + "column_order": 1 + }, + { + "data_path": "action_result.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric" + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.0.Threat Level", + "data_type": "string", + "column_name": "threat level", + "column_order": 2 + }, + { + "data_path": "action_result.data.1.Threat Categories", + "data_type": "string", + "column_name": "threat categories", + "column_order": 3 + }, + { + "data_path": "action_result.data.2.Acceptable Use Policy Categories", + "data_type": "string", + "column_name": "Acceptable Use Policy Categories", + "column_order": 4 + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + } + ], + "copied_from_id": 190, + "copied_from_version": "1.0.0", + "directory": "ciscotalosintelligence_7c653487-22c8-4ec1-bca0-16a8b1513c86", + "version": 1, + "appname": "-", + "executable": "spawn3", + "disabled": false, + "custom_made": true, + "pip39_dependencies": { + "wheel": [ + { + "module": "anyio", + "input_file": "wheels/py3/anyio-4.4.0-py3-none-any.whl" + }, + { + "module": "exceptiongroup", + "input_file": "wheels/py3/exceptiongroup-1.2.2-py3-none-any.whl" + }, + { + "module": "h11", + "input_file": "wheels/py3/h11-0.14.0-py3-none-any.whl" + }, + { + "module": "h2", + "input_file": "wheels/py3/h2-4.1.0-py3-none-any.whl" + }, + { + "module": "hpack", + "input_file": "wheels/py3/hpack-4.0.0-py3-none-any.whl" + }, + { + "module": "httpcore", + "input_file": "wheels/py3/httpcore-1.0.5-py3-none-any.whl" + }, + { + "module": "httpx", + "input_file": "wheels/py3/httpx-0.27.2-py3-none-any.whl" + }, + { + "module": "hyperframe", + "input_file": "wheels/py3/hyperframe-6.0.1-py3-none-any.whl" + }, + { + "module": "pydantic", + "input_file": "wheels/py39/pydantic-1.10.13-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" + }, + { + "module": "sniffio", + "input_file": "wheels/py3/sniffio-1.3.1-py3-none-any.whl" + }, + { + "module": "typing_extensions", + "input_file": "wheels/py3/typing_extensions-4.12.2-py3-none-any.whl" + } + ] + } +} diff --git a/ciscotalosintelligence_connector.py b/ciscotalosintelligence_connector.py new file mode 100644 index 0000000..61deee7 --- /dev/null +++ b/ciscotalosintelligence_connector.py @@ -0,0 +1,455 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# ----------------------------------------- +# Phantom sample App Connector python file +# ----------------------------------------- + +# Python 3 Compatibility imports +from __future__ import print_function, unicode_literals + +import ipaddress +import json +import os +import tempfile + +import httpx +# Phantom App imports +import phantom.app as phantom +import requests +from bs4 import BeautifulSoup +from phantom.action_result import ActionResult +from phantom.base_connector import BaseConnector + +from ciscotalosintelligence_consts import * + + +class RetVal(tuple): + def __new__(cls, val1, val2=None): + return tuple.__new__(RetVal, (val1, val2)) + + +class TalosIntelligenceConnector(BaseConnector): + def __init__(self): + super(TalosIntelligenceConnector, self).__init__() + + self._state = None + + self._base_url = None + self._certificate = None + self._key = None + + self._appinfo = None + self._catalog_id = 2 + + def _process_empty_response(self, response, action_result): + if response.status_code == 200: + return RetVal(phantom.APP_SUCCESS, {}) + + return RetVal( + action_result.set_status( + phantom.APP_ERROR, "Empty response and no information in the header" + ), + None, + ) + + def _process_html_response(self, response, action_result): + # An html response, treat it like an error + status_code = response.status_code + + try: + soup = BeautifulSoup(response.text, "html.parser") + error_text = soup.text + split_lines = error_text.split("\n") + split_lines = [x.strip() for x in split_lines if x.strip()] + error_text = "\n".join(split_lines) + except: + error_text = "Cannot parse error details" + + message = "Status Code: {0}. Data from server:\n{1}\n".format( + status_code, error_text + ) + + message = message.replace("{", "{{").replace("}", "}}") + return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) + + def _process_json_response(self, r, action_result): + # Try a json parse + try: + resp_json = r.json() + except Exception as e: + return RetVal( + action_result.set_status( + phantom.APP_ERROR, + "Unable to parse JSON response. Error: {0}".format(str(e)), + ), + None, + ) + + # Please specify the status codes here + if 200 <= r.status_code < 399: + return RetVal(phantom.APP_SUCCESS, resp_json) + + # You should process the error returned in the json + message = "Error from server. Status Code: {0} Data from server: {1}".format( + r.status_code, r.text.replace("{", "{{").replace("}", "}}") + ) + + return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) + + def _process_response(self, r, action_result): + # store the r_text in debug data, it will get dumped in the logs if the action fails + if hasattr(action_result, "add_debug_data"): + action_result.add_debug_data({"r_status_code": r.status_code}) + action_result.add_debug_data({"r_text": r.text}) + action_result.add_debug_data({"r_headers": r.headers}) + + # Process each 'Content-Type' of response separately + + # Process a json response + if "json" in r.headers.get("Content-Type", ""): + return self._process_json_response(r, action_result) + + # Process an HTML response, Do this no matter what the api talks. + # There is a high chance of a PROXY in between phantom and the rest of + # world, in case of errors, PROXY's return HTML, this function parses + # the error and adds it to the action_result. + if "html" in r.headers.get("Content-Type", ""): + return self._process_html_response(r, action_result) + + # it's not content-type that is to be parsed, handle an empty response + if not r.text: + return self._process_empty_response(r, action_result) + + # everything else is actually an error at this point + message = "Can't process response from server. Status Code: {0} Data from server: {1}".format( + r.status_code, r.text.replace("{", "{{").replace("}", "}}") + ) + + return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) + + def _make_rest_call(self, endpoint, action_result, method="get", **kwargs): + # **kwargs can be any additional parameters that requests.request accepts + + config = self.get_config() + + resp_json = None + + # Create a URL to connect to + url = self._base_url + endpoint + + with tempfile.NamedTemporaryFile( + mode="w+", delete=False, suffix="test" + ) as temp_file: + combined_file = ( + "-----BEGIN CERTIFICATE-----\n" + f"{self._certificate}\n" + "-----END CERTIFICATE-----\n" + "-----BEGIN RSA PRIVATE KEY-----\n" # pragma: allowlist secret + f"{self._key}\n" + "-----END RSA PRIVATE KEY-----\n" + ) + + temp_file.write(combined_file) + temp_file.seek(0) # Move the file pointer to the beginning for reading + temp_file_path = temp_file.name # Get the name of the temporary file + try: + client = httpx.Client( + http2=True, + verify=config.get("verify_server_cert", False), + cert=temp_file_path, + ) + request_func = getattr(client, method) + + r = request_func(url, **kwargs) + + except Exception as e: + if os.path.exists(temp_file_path): + os.remove(temp_file_path) + + return RetVal( + action_result.set_status( + phantom.APP_ERROR, + "Error Connecting to server. Details: {0}".format(str(e)), + ), + resp_json, + ) + + if os.path.exists(temp_file_path): + os.remove(temp_file_path) + + return self._process_response(r, action_result) + + def _handle_test_connectivity(self, param): + action_result = self.add_action_result(ActionResult(dict(param))) + self.save_progress("Connecting to endpoint") + + ret_val, response = self._make_rest_call( + ENDPOINT_QUERY_AUP_CAT_MAP, + action_result, + "post", + json={"app_info": self._appinfo}, + ) + + if phantom.is_fail(ret_val): + self.save_progress("Test Connectivity Failed.") + return action_result.get_status() + + self.save_progress("Received Metadata") + self.save_progress("Test Connectivity Passed") + + self._state = {} + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_ip_reputation(self, param): + self.save_progress( + "In action handler for: {0}".format(self.get_action_identifier()) + ) + action_result = self.add_action_result(ActionResult(dict(param))) + + ip = param["ip"] + + try: + ip_addr = ipaddress.ip_address(ip) + big_endian = int(ip_addr) + + except Exception: + return action_result.set_status( + phantom.APP_ERROR, "Please provide a valid IP Address" + ) + + payload = { + "urls": {"endpoint": [{"ipv4_addr": big_endian}]}, + "app_info": self._appinfo, + } + + self._query_reputation(action_result, payload) + + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_domain_reputation(self, param): + self.save_progress( + "In action handler for: {0}".format(self.get_action_identifier()) + ) + action_result = self.add_action_result(ActionResult(dict(param))) + + domain = param["domain"] + + payload = {"urls": [{"raw_url": domain}], "app_info": self._appinfo} + + self._query_reputation(action_result, payload) + + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_url_reputation(self, param): + self.save_progress( + "In action handler for: {0}".format(self.get_action_identifier()) + ) + action_result = self.add_action_result(ActionResult(dict(param))) + + url = param["url"] + + payload = {"urls": [{"raw_url": url}], "app_info": self._appinfo} + + self._query_reputation(action_result, payload) + + return action_result.set_status(phantom.APP_SUCCESS) + + def _query_reputation(self, action_result, payload): + taxonomy_ret_val, taxonomy = self._fetch_taxonomy(action_result) + + if phantom.is_fail(taxonomy_ret_val): + return action_result.get_status() + + # make rest call + ret_val, response = self._make_rest_call( + ENDPOINT_QUERY_REPUTATION_V3, action_result, method="post", json=payload + ) + response_taxonomy_map_version = response["taxonomy_map_version"] + + if response_taxonomy_map_version > self._state["taxonomy_version"]: + taxonomy_ret_val, taxonomy = self._fetch_taxonomy( + action_result, allow_cache=False + ) + + if phantom.is_fail(ret_val) or "results" not in response: + return action_result.get_status() + + summary = action_result.update_summary({}) + + threat_level = "" + threat_categories = {} + aup_categories = {} + + for result in response["results"]: + for url_result in result["results"]: + for tag in url_result["context_tags"]: + tax_id = str(tag["taxonomy_id"]) + entry_id = str(tag["taxonomy_entry_id"]) + + if tax_id not in taxonomy["taxonomies"]: + continue + + category = taxonomy["taxonomies"][tax_id]["name"]["en-us"]["text"] + name = taxonomy["taxonomies"][tax_id]["entries"][entry_id]["name"][ + "en-us" + ]["text"] + description = taxonomy["taxonomies"][tax_id]["entries"][entry_id][ + "description" + ]["en-us"]["text"] + + if category == "Threat Levels": + threat_level = name + elif category == "Threat Categories": + threat_categories[name] = description + elif category == "Acceptable Use Policy Categories": + aup_categories[name] = description + + summary["Threat Levels"] = threat_level + action_result.add_data({"Threat Level": threat_level}) + + summary["Threat Categories"] = threat_categories + action_result.add_data( + {"Threat Categories": ", ".join(list(threat_categories.keys()))} + ) + + summary["Acceptable Use Policy Categories"] = aup_categories + action_result.add_data( + { + "Acceptable Use Policy Categories": ", ".join( + list(aup_categories.keys()) + ) + } + ) + + def _fetch_taxonomy(self, action_result, allow_cache=True): + payload = {"app_info": self._appinfo} + + if "taxonomy" in self._state and allow_cache: + return 1, self._state["taxonomy"] + + ret_val, response = self._make_rest_call( + ENDPOINT_QUERY_TAXONOMIES, action_result, method="post", json=payload + ) + taxonomy = response["catalogs"][str(self._catalog_id)] + + self._state = {"taxonomy": taxonomy, "taxonomy_version": response["version"]} + + return ret_val, taxonomy + + def handle_action(self, param): + ret_val = phantom.APP_SUCCESS + + action_id = self.get_action_identifier() + + self.debug_print("action_id", self.get_action_identifier()) + + if action_id == "ip_reputation": + ret_val = self._handle_ip_reputation(param) + + if action_id == "domain_reputation": + ret_val = self._handle_domain_reputation(param) + + if action_id == "url_reputation": + ret_val = self._handle_url_reputation(param) + + if action_id == "test_connectivity": + ret_val = self._handle_test_connectivity(param) + + return ret_val + + def initialize(self): + # Load the state in initialize, use it to store data + # that needs to be accessed across actions + self._state = self.load_state() + + # get the asset config + config = self.get_config() + + def insert_newlines(string, every=64): + return "\n".join( + string[i: i + every] for i in range(0, len(string), every) + ) + + self._base_url = config["base_url"] + self._certificate = insert_newlines(config["certificate"]) + self._key = insert_newlines(config["key"]) + + self._appinfo = { + "product_family": "splunk", + "product_id": "soar", + "device_id": self.get_product_installation_id(), + "product_version": self.get_product_version(), + } + + return phantom.APP_SUCCESS + + def finalize(self): + self.save_state(self._state) + return phantom.APP_SUCCESS + + +def main(): + import argparse + + 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) + + args = argparser.parse_args() + session_id = None + + username = args.username + password = args.password + + 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: + try: + login_url = TalosIntelligenceConnector._get_phantom_base_url() + "/login" + + print("Accessing the Login page") + r = requests.get(login_url, verify=False) + 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=False, data=data, headers=headers) + session_id = r2.cookies["sessionid"] + except Exception as e: + print("Unable to get session id from the platform. Error: " + str(e)) + exit(1) + + with open(args.input_test_json) as f: + in_json = f.read() + in_json = json.loads(in_json) + print(json.dumps(in_json, indent=4)) + + connector = TalosIntelligenceConnector() + connector.print_progress_message = True + + if session_id is not None: + in_json["user_session_token"] = session_id + connector._set_csrf_info(csrftoken, headers["Referer"]) + + ret_val = connector._handle_action(json.dumps(in_json), None) + print(json.dumps(json.loads(ret_val), indent=4)) + + exit(0) + + +if __name__ == "__main__": + main() diff --git a/ciscotalosintelligence_consts.py b/ciscotalosintelligence_consts.py new file mode 100644 index 0000000..ffc19d2 --- /dev/null +++ b/ciscotalosintelligence_consts.py @@ -0,0 +1,6 @@ +# Define your constants here + + +ENDPOINT_QUERY_REPUTATION_V3 = "/Talos.Service.URS/QueryReputationV3" +ENDPOINT_QUERY_AUP_CAT_MAP = "/Talos.Service.URS/QueryAUPCatMap" +ENDPOINT_QUERY_TAXONOMIES = "/Talos.Service.TTS/QueryTaxonomyCatalogs" From bd1f174be29f9fabb4919fb32bba5aa83155054e Mon Sep 17 00:00:00 2001 From: splunk-soar-connectors-admin Date: Tue, 8 Oct 2024 20:25:08 +0000 Subject: [PATCH 07/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76e11fc..e403afb 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Product Name: Talos Cloud Intelligence Product Version Supported (regex): ".\*" Minimum Product Version: 6.2.1.305 -This app provides investigative actions for Talos Cloud Intelligence +This app provides investigative actions for Cisco Talos Cloud Intelligence [comment]: # " File: README.md" [comment]: # "Copyright (c) 2024 Splunk Inc." From 556f27a3b0f00c94d664bd2a1ce1044f6a04d961 Mon Sep 17 00:00:00 2001 From: splunk-soar-connectors-admin Date: Wed, 30 Oct 2024 21:37:26 +0000 Subject: [PATCH 08/21] Update README.md --- README.md | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e403afb..3087826 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ [comment]: # "Auto-generated SOAR connector documentation" # Cisco Talos Intelligence -Publisher: Splunk +Publisher: Splunk Community Connector Version: 1.0.1 Product Vendor: Cisco Product Name: Talos Cloud Intelligence Product Version Supported (regex): ".\*" Minimum Product Version: 6.2.1.305 -This app provides investigative actions for Cisco Talos Cloud Intelligence +This app provides investigative actions for Cisco Talos Intelligence [comment]: # " File: README.md" [comment]: # "Copyright (c) 2024 Splunk Inc." @@ -42,13 +42,12 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION **base_url** | required | string | Base URL provided by Talos **certificate** | optional | password | Certificate contents to authenticate with Talos **key** | optional | password | Private key to authenticate with Talos -**verify_server_cert** | optional | boolean | Verify server certificate ### Supported Actions [test connectivity](#action-test-connectivity) - Validate the asset configuration for connectivity using supplied configuration -[ip reputation](#action-ip-reputation) - Query IP info -[domain reputation](#action-domain-reputation) - Query domain info -[url reputation](#action-url-reputation) - Query URL info +[ip reputation](#action-ip-reputation) - Queries IP info +[domain reputation](#action-domain-reputation) - Queries domain info +[url reputation](#action-url-reputation) - Queries URL info ## action: 'test connectivity' Validate the asset configuration for connectivity using supplied configuration @@ -56,8 +55,6 @@ Validate the asset configuration for connectivity using supplied configuration Type: **test** Read only: **True** -Action uses the URS API to get a list of the AUP categories used to classify website content. - #### Action Parameters No parameters are required for this action @@ -65,13 +62,11 @@ No parameters are required for this action No Output ## action: 'ip reputation' -Query IP info +Queries IP info Type: **investigate** Read only: **True** -Provide information on an IP address's reputation, enabling you to take proper action against untrusted, and unwanted resources. - #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- @@ -90,13 +85,11 @@ action_result.data.1.Threat Categories | string | | action_result.data.2.Acceptable Use Policy Categories | string | | ## action: 'domain reputation' -Query domain info +Queries domain info Type: **investigate** Read only: **True** -Provide information on a domain's reputation, enabling you to take proper action against untrusted, and unwanted resources. - #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- @@ -115,13 +108,11 @@ action_result.data.1.Threat Categories | string | | action_result.data.2.Acceptable Use Policy Categories | string | | ## action: 'url reputation' -Query URL info +Queries URL info Type: **investigate** Read only: **True** -Provide information on an URL's reputation, enabling you to take proper action against untrusted, and unwanted resources. - #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- From bee72ed20108005474b8f6a4cb85008bd95b955d Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 15:42:03 -0600 Subject: [PATCH 09/21] dedup json --- ciscotalosintelligence.json | 338 ------------------------------------ 1 file changed, 338 deletions(-) delete mode 100644 ciscotalosintelligence.json diff --git a/ciscotalosintelligence.json b/ciscotalosintelligence.json deleted file mode 100644 index dd00969..0000000 --- a/ciscotalosintelligence.json +++ /dev/null @@ -1,338 +0,0 @@ -{ - "appid": "7c653487-22c8-4ec1-bca0-16a8b1513c86", - "name": "Cisco Talos Intelligence", - "description": "This app provides investigative actions for Cisco Talos Intelligence", - "type": "information", - "product_vendor": "Cisco", - "logo": "ciscotalosintelligence.svg", - "logo_dark": "ciscotalosintelligence_dark.svg", - "product_name": "Talos Cloud Intelligence", - "python_version": "3", - "product_version_regex": ".*", - "publisher": "Splunk Community", - "license": "Copyright (c) Splunk Community, 2024", - "app_version": "1.0.1", - "utctime_updated": "2024-06-21T18:40:03.685771Z", - "package_name": "phantom_ciscotalosintelligence", - "main_module": "ciscotalosintelligence_connector.py", - "min_phantom_version": "6.2.1.305", - "app_wizard_version": "1.0.0", - "configuration": { - "base_url": { - "data_type": "string", - "order": 0, - "description": "Base URL provided by Talos", - "default": "consumerdev-api.talos.cisco.com", - "required": true, - "name": "base_url", - "id": 0 - }, - "certificate": { - "data_type": "password", - "order": 1, - "description": "Certificate contents to authenticate with Talos", - "name": "certificate", - "id": 1 - }, - "key": { - "data_type": "password", - "order": 2, - "description": "Private key to authenticate with Talos", - "name": "key", - "id": 2 - } - }, - "actions": [ - { - "action": "test connectivity", - "identifier": "test_connectivity", - "description": "Validate the asset configuration for connectivity using supplied configuration", - "verbose": "", - "type": "test", - "read_only": true, - "parameters": {}, - "output": [], - "versions": "EQ(*)" - }, - { - "action": "ip reputation", - "identifier": "ip_reputation", - "description": "Queries IP info", - "verbose": "", - "type": "investigate", - "read_only": true, - "parameters": { - "ip": { - "description": "IP to query", - "data_type": "string", - "required": true, - "primary": true, - "contains": [ - "ip" - ], - "value_list": [], - "default": "", - "order": 0, - "name": "ip" - } - }, - "output": [ - { - "data_path": "action_result.parameter.ip", - "data_type": "string", - "contains": [ - "ip" - ], - "column_name": "ip", - "column_order": 0 - }, - { - "data_path": "action_result.status", - "data_type": "string", - "column_name": "status", - "column_order": 1 - }, - { - "data_path": "action_result.message", - "data_type": "string" - }, - { - "data_path": "summary.total_objects", - "data_type": "numeric" - }, - { - "data_path": "summary.total_objects_successful", - "data_type": "numeric" - }, - { - "data_path": "action_result.data.0.Threat Level", - "data_type": "string", - "column_name": "threat level", - "column_order": 2 - }, - { - "data_path": "action_result.data.1.Threat Categories", - "data_type": "string", - "column_name": "threat categories", - "column_order": 3 - }, - { - "data_path": "action_result.data.2.Acceptable Use Policy Categories", - "data_type": "string", - "column_name": "Acceptable Use Policy Categories", - "column_order": 4 - } - ], - "render": { - "type": "table" - }, - "versions": "EQ(*)" - }, - { - "action": "domain reputation", - "identifier": "domain_reputation", - "description": "Queries domain info", - "verbose": "", - "type": "investigate", - "read_only": true, - "parameters": { - "domain": { - "description": "Domain to query", - "data_type": "string", - "required": true, - "primary": true, - "contains": [ - "domain", - "url" - ], - "value_list": [], - "default": "", - "order": 0, - "name": "domain" - } - }, - "output": [ - { - "data_path": "action_result.parameter.domain", - "data_type": "string", - "contains": [ - "domain", - "url" - ], - "column_name": "domain", - "column_order": 0 - }, - { - "data_path": "action_result.status", - "data_type": "string", - "column_name": "status", - "column_order": 1 - }, - { - "data_path": "action_result.message", - "data_type": "string" - }, - { - "data_path": "summary.total_objects", - "data_type": "numeric" - }, - { - "data_path": "summary.total_objects_successful", - "data_type": "numeric" - }, - { - "data_path": "action_result.data.0.Threat Level", - "data_type": "string", - "column_name": "threat level", - "column_order": 2 - }, - { - "data_path": "action_result.data.1.Threat Categories", - "data_type": "string", - "column_name": "threat categories", - "column_order": 3 - }, - { - "data_path": "action_result.data.2.Acceptable Use Policy Categories", - "data_type": "string", - "column_name": "Acceptable Use Policy Categories", - "column_order": 4 - } - ], - "render": { - "type": "table" - }, - "versions": "EQ(*)" - }, - { - "action": "url reputation", - "identifier": "url_reputation", - "description": "Queries URL info", - "verbose": "", - "type": "investigate", - "read_only": true, - "parameters": { - "url": { - "description": "URL to query", - "data_type": "string", - "required": true, - "primary": true, - "contains": [ - "url" - ], - "value_list": [], - "default": "", - "order": 0, - "name": "url" - } - }, - "output": [ - { - "data_path": "action_result.parameter.url", - "data_type": "string", - "contains": [ - "url" - ], - "column_name": "url", - "column_order": 0 - }, - { - "data_path": "action_result.status", - "data_type": "string", - "column_name": "status", - "column_order": 1 - }, - { - "data_path": "action_result.message", - "data_type": "string" - }, - { - "data_path": "summary.total_objects", - "data_type": "numeric" - }, - { - "data_path": "summary.total_objects_successful", - "data_type": "numeric" - }, - { - "data_path": "action_result.data.0.Threat Level", - "data_type": "string", - "column_name": "threat level", - "column_order": 2 - }, - { - "data_path": "action_result.data.1.Threat Categories", - "data_type": "string", - "column_name": "threat categories", - "column_order": 3 - }, - { - "data_path": "action_result.data.2.Acceptable Use Policy Categories", - "data_type": "string", - "column_name": "Acceptable Use Policy Categories", - "column_order": 4 - } - ], - "render": { - "type": "table" - }, - "versions": "EQ(*)" - } - ], - "copied_from_id": 190, - "copied_from_version": "1.0.0", - "directory": "ciscotalosintelligence_7c653487-22c8-4ec1-bca0-16a8b1513c86", - "version": 1, - "appname": "-", - "executable": "spawn3", - "disabled": false, - "custom_made": true, - "pip39_dependencies": { - "wheel": [ - { - "module": "anyio", - "input_file": "wheels/py3/anyio-4.4.0-py3-none-any.whl" - }, - { - "module": "exceptiongroup", - "input_file": "wheels/py3/exceptiongroup-1.2.2-py3-none-any.whl" - }, - { - "module": "h11", - "input_file": "wheels/py3/h11-0.14.0-py3-none-any.whl" - }, - { - "module": "h2", - "input_file": "wheels/py3/h2-4.1.0-py3-none-any.whl" - }, - { - "module": "hpack", - "input_file": "wheels/py3/hpack-4.0.0-py3-none-any.whl" - }, - { - "module": "httpcore", - "input_file": "wheels/py3/httpcore-1.0.5-py3-none-any.whl" - }, - { - "module": "httpx", - "input_file": "wheels/py3/httpx-0.27.2-py3-none-any.whl" - }, - { - "module": "hyperframe", - "input_file": "wheels/py3/hyperframe-6.0.1-py3-none-any.whl" - }, - { - "module": "pydantic", - "input_file": "wheels/py39/pydantic-1.10.13-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" - }, - { - "module": "sniffio", - "input_file": "wheels/py3/sniffio-1.3.1-py3-none-any.whl" - }, - { - "module": "typing_extensions", - "input_file": "wheels/py3/typing_extensions-4.12.2-py3-none-any.whl" - } - ] - } -} From a627548219c79aa618123ae81d6c6528126ea436 Mon Sep 17 00:00:00 2001 From: splunk-soar-connectors-admin Date: Wed, 30 Oct 2024 21:42:51 +0000 Subject: [PATCH 10/21] Update README.md --- README.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3087826..76e11fc 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ [comment]: # "Auto-generated SOAR connector documentation" # Cisco Talos Intelligence -Publisher: Splunk Community +Publisher: Splunk Connector Version: 1.0.1 Product Vendor: Cisco Product Name: Talos Cloud Intelligence Product Version Supported (regex): ".\*" Minimum Product Version: 6.2.1.305 -This app provides investigative actions for Cisco Talos Intelligence +This app provides investigative actions for Talos Cloud Intelligence [comment]: # " File: README.md" [comment]: # "Copyright (c) 2024 Splunk Inc." @@ -42,12 +42,13 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION **base_url** | required | string | Base URL provided by Talos **certificate** | optional | password | Certificate contents to authenticate with Talos **key** | optional | password | Private key to authenticate with Talos +**verify_server_cert** | optional | boolean | Verify server certificate ### Supported Actions [test connectivity](#action-test-connectivity) - Validate the asset configuration for connectivity using supplied configuration -[ip reputation](#action-ip-reputation) - Queries IP info -[domain reputation](#action-domain-reputation) - Queries domain info -[url reputation](#action-url-reputation) - Queries URL info +[ip reputation](#action-ip-reputation) - Query IP info +[domain reputation](#action-domain-reputation) - Query domain info +[url reputation](#action-url-reputation) - Query URL info ## action: 'test connectivity' Validate the asset configuration for connectivity using supplied configuration @@ -55,6 +56,8 @@ Validate the asset configuration for connectivity using supplied configuration Type: **test** Read only: **True** +Action uses the URS API to get a list of the AUP categories used to classify website content. + #### Action Parameters No parameters are required for this action @@ -62,11 +65,13 @@ No parameters are required for this action No Output ## action: 'ip reputation' -Queries IP info +Query IP info Type: **investigate** Read only: **True** +Provide information on an IP address's reputation, enabling you to take proper action against untrusted, and unwanted resources. + #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- @@ -85,11 +90,13 @@ action_result.data.1.Threat Categories | string | | action_result.data.2.Acceptable Use Policy Categories | string | | ## action: 'domain reputation' -Queries domain info +Query domain info Type: **investigate** Read only: **True** +Provide information on a domain's reputation, enabling you to take proper action against untrusted, and unwanted resources. + #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- @@ -108,11 +115,13 @@ action_result.data.1.Threat Categories | string | | action_result.data.2.Acceptable Use Policy Categories | string | | ## action: 'url reputation' -Queries URL info +Query URL info Type: **investigate** Read only: **True** +Provide information on an URL's reputation, enabling you to take proper action against untrusted, and unwanted resources. + #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- From c55f3883aa4ac797d025261c57f71389577e80d0 Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 15:45:32 -0600 Subject: [PATCH 11/21] rename more gooder --- ...igence.json => ciscotalosintelligence.json | 34 +- talosintelligence_connector.py | 428 ------------------ talosintelligence_consts.py | 20 - 3 files changed, 12 insertions(+), 470 deletions(-) rename talosintelligence.json => ciscotalosintelligence.json (90%) delete mode 100644 talosintelligence_connector.py delete mode 100644 talosintelligence_consts.py diff --git a/talosintelligence.json b/ciscotalosintelligence.json similarity index 90% rename from talosintelligence.json rename to ciscotalosintelligence.json index 3b8d53c..dd00969 100644 --- a/talosintelligence.json +++ b/ciscotalosintelligence.json @@ -1,24 +1,20 @@ { "appid": "7c653487-22c8-4ec1-bca0-16a8b1513c86", "name": "Cisco Talos Intelligence", - "description": "This app provides investigative actions for Talos Cloud Intelligence", + "description": "This app provides investigative actions for Cisco Talos Intelligence", "type": "information", "product_vendor": "Cisco", "logo": "ciscotalosintelligence.svg", "logo_dark": "ciscotalosintelligence_dark.svg", "product_name": "Talos Cloud Intelligence", "python_version": "3", - "latest_tested_versions": [ - "Cloud, Sept 9, 2024" - ], "product_version_regex": ".*", - "publisher": "Splunk", - "license": "Copyright (c) 2024 Splunk Inc.", + "publisher": "Splunk Community", + "license": "Copyright (c) Splunk Community, 2024", "app_version": "1.0.1", "utctime_updated": "2024-06-21T18:40:03.685771Z", "package_name": "phantom_ciscotalosintelligence", - "fips_compliant": false, - "main_module": "talosintelligence_connector.py", + "main_module": "ciscotalosintelligence_connector.py", "min_phantom_version": "6.2.1.305", "app_wizard_version": "1.0.0", "configuration": { @@ -44,12 +40,6 @@ "description": "Private key to authenticate with Talos", "name": "key", "id": 2 - }, - "verify_server_cert": { - "description": "Verify server certificate", - "data_type": "boolean", - "default": false, - "order": 3 } }, "actions": [ @@ -57,7 +47,7 @@ "action": "test connectivity", "identifier": "test_connectivity", "description": "Validate the asset configuration for connectivity using supplied configuration", - "verbose": "Action uses the URS API to get a list of the AUP categories used to classify website content.", + "verbose": "", "type": "test", "read_only": true, "parameters": {}, @@ -67,8 +57,8 @@ { "action": "ip reputation", "identifier": "ip_reputation", - "description": "Query IP info", - "verbose": "Provide information on an IP address's reputation, enabling you to take proper action against untrusted, and unwanted resources.", + "description": "Queries IP info", + "verbose": "", "type": "investigate", "read_only": true, "parameters": { @@ -141,8 +131,8 @@ { "action": "domain reputation", "identifier": "domain_reputation", - "description": "Query domain info", - "verbose": "Provide information on a domain's reputation, enabling you to take proper action against untrusted, and unwanted resources.", + "description": "Queries domain info", + "verbose": "", "type": "investigate", "read_only": true, "parameters": { @@ -217,8 +207,8 @@ { "action": "url reputation", "identifier": "url_reputation", - "description": "Query URL info", - "verbose": "Provide information on an URL's reputation, enabling you to take proper action against untrusted, and unwanted resources.", + "description": "Queries URL info", + "verbose": "", "type": "investigate", "read_only": true, "parameters": { @@ -291,7 +281,7 @@ ], "copied_from_id": 190, "copied_from_version": "1.0.0", - "directory": "talosintelligence_50612f98-f830-451b-a90c-26cf82593a54", + "directory": "ciscotalosintelligence_7c653487-22c8-4ec1-bca0-16a8b1513c86", "version": 1, "appname": "-", "executable": "spawn3", diff --git a/talosintelligence_connector.py b/talosintelligence_connector.py deleted file mode 100644 index d2f1216..0000000 --- a/talosintelligence_connector.py +++ /dev/null @@ -1,428 +0,0 @@ -# File: talosintelligence_connector.py -# -# Copyright (c) 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software 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. -# -# -# Phantom App imports -import ipaddress -import json -import os -import tempfile - -import httpx - -# Phantom App imports -import phantom.app as phantom -import requests -from bs4 import BeautifulSoup -from phantom.action_result import ActionResult -from phantom.base_connector import BaseConnector - -from talosintelligence_consts import * - - -class RetVal(tuple): - def __new__(cls, val1, val2=None): - return tuple.__new__(RetVal, (val1, val2)) - - -class TalosIntelligenceConnector(BaseConnector): - def __init__(self): - super(TalosIntelligenceConnector, self).__init__() - - self._state = None - - self._base_url = None - self._certificate = None - self._key = None - - self._appinfo = None - self._catalog_id = 2 - - def _process_empty_response(self, response, action_result): - if response.status_code == 200: - return RetVal(phantom.APP_SUCCESS, {}) - - return RetVal( - action_result.set_status(phantom.APP_ERROR, "Empty response and no information in the header"), - None, - ) - - def _process_html_response(self, response, action_result): - # An html response, treat it like an error - status_code = response.status_code - - try: - soup = BeautifulSoup(response.text, "html.parser") - error_text = soup.text - split_lines = error_text.split("\n") - split_lines = [x.strip() for x in split_lines if x.strip()] - error_text = "\n".join(split_lines) - except: - error_text = "Cannot parse error details" - - message = "Status Code: {0}. Data from server:\n{1}\n".format(status_code, error_text) - - message = message.replace("{", "{{").replace("}", "}}") - return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) - - def _process_json_response(self, r, action_result): - # Try a json parse - try: - resp_json = r.json() - except Exception as e: - return RetVal( - action_result.set_status( - phantom.APP_ERROR, - "Unable to parse JSON response. Error: {0}".format(str(e)), - ), - None, - ) - - # Please specify the status codes here - if 200 <= r.status_code < 399: - return RetVal(phantom.APP_SUCCESS, resp_json) - - # You should process the error returned in the json - message = "Error from server. Status Code: {0} Data from server: {1}".format(r.status_code, r.text.replace("{", "{{").replace("}", "}}")) - - return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) - - def _process_response(self, r, action_result): - # store the r_text in debug data, it will get dumped in the logs if the action fails - if hasattr(action_result, "add_debug_data"): - action_result.add_debug_data({"r_status_code": r.status_code}) - action_result.add_debug_data({"r_text": r.text}) - action_result.add_debug_data({"r_headers": r.headers}) - - # Process each 'Content-Type' of response separately - - # Process a json response - if "json" in r.headers.get("Content-Type", ""): - return self._process_json_response(r, action_result) - - # Process an HTML response, Do this no matter what the api talks. - # There is a high chance of a PROXY in between phantom and the rest of - # world, in case of errors, PROXY's return HTML, this function parses - # the error and adds it to the action_result. - if "html" in r.headers.get("Content-Type", ""): - return self._process_html_response(r, action_result) - - # it's not content-type that is to be parsed, handle an empty response - if not r.text: - return self._process_empty_response(r, action_result) - - # everything else is actually an error at this point - message = "Can't process response from server. Status Code: {0} Data from server: {1}".format( - r.status_code, r.text.replace("{", "{{").replace("}", "}}") - ) - - return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) - - def _make_rest_call(self, endpoint, action_result, method="get", **kwargs): - # **kwargs can be any additional parameters that requests.request accepts - - config = self.get_config() - - resp_json = None - - # Create a URL to connect to - url = self._base_url + endpoint - - with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix="test") as temp_file: - combined_file = ( - "-----BEGIN CERTIFICATE-----\n" - f"{self._certificate}\n" - "-----END CERTIFICATE-----\n" - "-----BEGIN RSA PRIVATE KEY-----\n" # pragma: allowlist secret - f"{self._key}\n" - "-----END RSA PRIVATE KEY-----\n" - ) - - temp_file.write(combined_file) - temp_file.seek(0) # Move the file pointer to the beginning for reading - temp_file_path = temp_file.name # Get the name of the temporary file - try: - client = httpx.Client( - http2=True, - verify=config.get("verify_server_cert", False), - cert=temp_file_path, - ) - request_func = getattr(client, method) - - r = request_func(url, **kwargs) - - except Exception as e: - if os.path.exists(temp_file_path): - os.remove(temp_file_path) - - return RetVal( - action_result.set_status( - phantom.APP_ERROR, - "Error Connecting to server. Details: {0}".format(str(e)), - ), - resp_json, - ) - - if os.path.exists(temp_file_path): - os.remove(temp_file_path) - - return self._process_response(r, action_result) - - def _handle_test_connectivity(self, param): - action_result = self.add_action_result(ActionResult(dict(param))) - self.save_progress("Connecting to endpoint") - - ret_val, response = self._make_rest_call( - ENDPOINT_QUERY_AUP_CAT_MAP, - action_result, - "post", - json={"app_info": self._appinfo}, - ) - - if phantom.is_fail(ret_val): - self.save_progress("Test Connectivity Failed.") - return action_result.get_status() - - self.save_progress("Received Metadata") - self.save_progress("Test Connectivity Passed") - - self._state = {} - return action_result.set_status(phantom.APP_SUCCESS) - - def _handle_ip_reputation(self, param): - self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) - action_result = self.add_action_result(ActionResult(dict(param))) - - ip = param["ip"] - - try: - ip_addr = ipaddress.ip_address(ip) - big_endian = int(ip_addr) - - except Exception: - return action_result.set_status(phantom.APP_ERROR, "Please provide a valid IP Address") - - payload = { - "urls": {"endpoint": [{"ipv4_addr": big_endian}]}, - "app_info": self._appinfo, - } - - self._query_reputation(action_result, payload) - - return action_result.set_status(phantom.APP_SUCCESS) - - def _handle_domain_reputation(self, param): - self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) - action_result = self.add_action_result(ActionResult(dict(param))) - - domain = param["domain"] - - payload = {"urls": [{"raw_url": domain}], "app_info": self._appinfo} - - self._query_reputation(action_result, payload) - - return action_result.set_status(phantom.APP_SUCCESS) - - def _handle_url_reputation(self, param): - self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) - action_result = self.add_action_result(ActionResult(dict(param))) - - url = param["url"] - - payload = {"urls": [{"raw_url": url}], "app_info": self._appinfo} - - self._query_reputation(action_result, payload) - - return action_result.set_status(phantom.APP_SUCCESS) - - def _query_reputation(self, action_result, payload): - taxonomy_ret_val, taxonomy = self._fetch_taxonomy(action_result) - - if phantom.is_fail(taxonomy_ret_val): - return action_result.get_status() - - # make rest call - ret_val, response = self._make_rest_call(ENDPOINT_QUERY_REPUTATION_V3, action_result, method="post", json=payload) - response_taxonomy_map_version = response["taxonomy_map_version"] - - if response_taxonomy_map_version > self._state["taxonomy_version"]: - taxonomy_ret_val, taxonomy = self._fetch_taxonomy(action_result, allow_cache=False) - - if phantom.is_fail(ret_val) or "results" not in response: - return action_result.get_status() - - summary = action_result.update_summary({}) - - threat_level = "" - threat_categories = {} - aup_categories = {} - - for result in response["results"]: - for url_result in result["results"]: - for tag in url_result["context_tags"]: - tax_id = str(tag["taxonomy_id"]) - entry_id = str(tag["taxonomy_entry_id"]) - - if tax_id not in taxonomy["taxonomies"]: - continue - - category = taxonomy["taxonomies"][tax_id]["name"]["en-us"]["text"] - name = taxonomy["taxonomies"][tax_id]["entries"][entry_id]["name"]["en-us"]["text"] - description = taxonomy["taxonomies"][tax_id]["entries"][entry_id]["description"]["en-us"]["text"] - - if category == "Threat Levels": - threat_level = name - elif category == "Threat Categories": - threat_categories[name] = description - elif category == "Acceptable Use Policy Categories": - aup_categories[name] = description - - summary["Threat Levels"] = threat_level - action_result.add_data({"Threat Level": threat_level}) - - summary["Threat Categories"] = threat_categories - action_result.add_data({"Threat Categories": ", ".join(list(threat_categories.keys()))}) - - summary["Acceptable Use Policy Categories"] = aup_categories - action_result.add_data({"Acceptable Use Policy Categories": ", ".join(list(aup_categories.keys()))}) - - def _fetch_taxonomy(self, action_result, allow_cache=True): - payload = {"app_info": self._appinfo} - - if "taxonomy" in self._state and allow_cache: - return 1, self._state["taxonomy"] - - ret_val, response = self._make_rest_call(ENDPOINT_QUERY_TAXONOMIES, action_result, method="post", json=payload) - taxonomy = response["catalogs"][str(self._catalog_id)] - - self._state = {"taxonomy": taxonomy, "taxonomy_version": response["version"]} - - return ret_val, taxonomy - - def handle_action(self, param): - ret_val = phantom.APP_SUCCESS - - action_id = self.get_action_identifier() - - self.debug_print("action_id", self.get_action_identifier()) - - if action_id == "ip_reputation": - ret_val = self._handle_ip_reputation(param) - - if action_id == "domain_reputation": - ret_val = self._handle_domain_reputation(param) - - if action_id == "url_reputation": - ret_val = self._handle_url_reputation(param) - - if action_id == "test_connectivity": - ret_val = self._handle_test_connectivity(param) - - return ret_val - - def initialize(self): - # Load the state in initialize, use it to store data - # that needs to be accessed across actions - self._state = self.load_state() - - # get the asset config - config = self.get_config() - - def insert_newlines(string, every=64): - return "\n".join(string[i : i + every] for i in range(0, len(string), every)) - - self._base_url = config["base_url"] - self._certificate = insert_newlines(config["certificate"]) - self._key = insert_newlines(config["key"]) - - self._appinfo = { - "product_family": "splunk", - "product_id": "soar", - "device_id": self.get_product_installation_id(), - "product_version": self.get_product_version(), - } - - return phantom.APP_SUCCESS - - def finalize(self): - self.save_state(self._state) - return phantom.APP_SUCCESS - - -def main(): - import argparse - - 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) - - args = argparser.parse_args() - session_id = None - - username = args.username - password = args.password - - 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: - try: - login_url = TalosIntelligenceConnector._get_phantom_base_url() + "/login" - - print("Accessing the Login page") - r = requests.get(login_url, verify=False) - 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=False, data=data, headers=headers) - session_id = r2.cookies["sessionid"] - except Exception as e: - print("Unable to get session id from the platform. Error: " + str(e)) - exit(1) - - with open(args.input_test_json) as f: - in_json = f.read() - in_json = json.loads(in_json) - print(json.dumps(in_json, indent=4)) - - connector = TalosIntelligenceConnector() - connector.print_progress_message = True - - if session_id is not None: - in_json["user_session_token"] = session_id - connector._set_csrf_info(csrftoken, headers["Referer"]) - - ret_val = connector._handle_action(json.dumps(in_json), None) - print(json.dumps(json.loads(ret_val), indent=4)) - - exit(0) - - -if __name__ == "__main__": - main() diff --git a/talosintelligence_consts.py b/talosintelligence_consts.py deleted file mode 100644 index 0cdf464..0000000 --- a/talosintelligence_consts.py +++ /dev/null @@ -1,20 +0,0 @@ -# File: talosintelligence_consts.py -# -# Copyright (c) 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software 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. -# -# -# Define your constants here -ENDPOINT_QUERY_REPUTATION_V3 = "/Talos.Service.URS/QueryReputationV3" -ENDPOINT_QUERY_AUP_CAT_MAP = "/Talos.Service.URS/QueryAUPCatMap" -ENDPOINT_QUERY_TAXONOMIES = "/Talos.Service.TTS/QueryTaxonomyCatalogs" From aff88a51dd9fc9ec0263fe3e1e3cfea9233d8715 Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 15:46:34 -0600 Subject: [PATCH 12/21] not community --- ciscotalosintelligence.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ciscotalosintelligence.json b/ciscotalosintelligence.json index dd00969..71261e4 100644 --- a/ciscotalosintelligence.json +++ b/ciscotalosintelligence.json @@ -9,8 +9,8 @@ "product_name": "Talos Cloud Intelligence", "python_version": "3", "product_version_regex": ".*", - "publisher": "Splunk Community", - "license": "Copyright (c) Splunk Community, 2024", + "publisher": "Splunk", + "license": "Copyright (c) Splunk, 2024", "app_version": "1.0.1", "utctime_updated": "2024-06-21T18:40:03.685771Z", "package_name": "phantom_ciscotalosintelligence", From 1e87501ea4926988f3c1e77f8ced0d5d05311355 Mon Sep 17 00:00:00 2001 From: splunk-soar-connectors-admin Date: Wed, 30 Oct 2024 21:47:12 +0000 Subject: [PATCH 13/21] Update README.md --- README.md | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 76e11fc..64443c1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Product Name: Talos Cloud Intelligence Product Version Supported (regex): ".\*" Minimum Product Version: 6.2.1.305 -This app provides investigative actions for Talos Cloud Intelligence +This app provides investigative actions for Cisco Talos Intelligence [comment]: # " File: README.md" [comment]: # "Copyright (c) 2024 Splunk Inc." @@ -42,13 +42,12 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION **base_url** | required | string | Base URL provided by Talos **certificate** | optional | password | Certificate contents to authenticate with Talos **key** | optional | password | Private key to authenticate with Talos -**verify_server_cert** | optional | boolean | Verify server certificate ### Supported Actions [test connectivity](#action-test-connectivity) - Validate the asset configuration for connectivity using supplied configuration -[ip reputation](#action-ip-reputation) - Query IP info -[domain reputation](#action-domain-reputation) - Query domain info -[url reputation](#action-url-reputation) - Query URL info +[ip reputation](#action-ip-reputation) - Queries IP info +[domain reputation](#action-domain-reputation) - Queries domain info +[url reputation](#action-url-reputation) - Queries URL info ## action: 'test connectivity' Validate the asset configuration for connectivity using supplied configuration @@ -56,8 +55,6 @@ Validate the asset configuration for connectivity using supplied configuration Type: **test** Read only: **True** -Action uses the URS API to get a list of the AUP categories used to classify website content. - #### Action Parameters No parameters are required for this action @@ -65,13 +62,11 @@ No parameters are required for this action No Output ## action: 'ip reputation' -Query IP info +Queries IP info Type: **investigate** Read only: **True** -Provide information on an IP address's reputation, enabling you to take proper action against untrusted, and unwanted resources. - #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- @@ -90,13 +85,11 @@ action_result.data.1.Threat Categories | string | | action_result.data.2.Acceptable Use Policy Categories | string | | ## action: 'domain reputation' -Query domain info +Queries domain info Type: **investigate** Read only: **True** -Provide information on a domain's reputation, enabling you to take proper action against untrusted, and unwanted resources. - #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- @@ -115,13 +108,11 @@ action_result.data.1.Threat Categories | string | | action_result.data.2.Acceptable Use Policy Categories | string | | ## action: 'url reputation' -Query URL info +Queries URL info Type: **investigate** Read only: **True** -Provide information on an URL's reputation, enabling you to take proper action against untrusted, and unwanted resources. - #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- From e2688ac689950c0d9625a32e4660c1fafd55864c Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 15:53:24 -0600 Subject: [PATCH 14/21] cleanup json --- ciscotalosintelligence.json | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/ciscotalosintelligence.json b/ciscotalosintelligence.json index 71261e4..3a4331c 100644 --- a/ciscotalosintelligence.json +++ b/ciscotalosintelligence.json @@ -1,19 +1,22 @@ { "appid": "7c653487-22c8-4ec1-bca0-16a8b1513c86", "name": "Cisco Talos Intelligence", - "description": "This app provides investigative actions for Cisco Talos Intelligence", + "description": "This app provides investigative actions for Cisco Talos Cloud Intelligence", "type": "information", "product_vendor": "Cisco", "logo": "ciscotalosintelligence.svg", "logo_dark": "ciscotalosintelligence_dark.svg", "product_name": "Talos Cloud Intelligence", - "python_version": "3", + "python_version": "3", "latest_tested_versions": [ + "Cloud, October 30, 2024" + ], "product_version_regex": ".*", "publisher": "Splunk", - "license": "Copyright (c) Splunk, 2024", + "license": "Copyright (c) 2024 Splunk Inc.", "app_version": "1.0.1", "utctime_updated": "2024-06-21T18:40:03.685771Z", "package_name": "phantom_ciscotalosintelligence", + "fips_compliant": false, "main_module": "ciscotalosintelligence_connector.py", "min_phantom_version": "6.2.1.305", "app_wizard_version": "1.0.0", @@ -40,6 +43,12 @@ "description": "Private key to authenticate with Talos", "name": "key", "id": 2 + }, + "verify_server_cert": { + "description": "Verify server certificate", + "data_type": "boolean", + "default": false, + "order": 3 } }, "actions": [ @@ -57,8 +66,8 @@ { "action": "ip reputation", "identifier": "ip_reputation", - "description": "Queries IP info", - "verbose": "", + "description": "Query IP info", + "verbose": "Provide information on an IP address's reputation, enabling you to take proper action against untrusted, and unwanted resources.", "type": "investigate", "read_only": true, "parameters": { @@ -131,8 +140,8 @@ { "action": "domain reputation", "identifier": "domain_reputation", - "description": "Queries domain info", - "verbose": "", + "description": "Query domain info", + "verbose": "Provide information on a domain's reputation, enabling you to take proper action against untrusted, and unwanted resources.", "type": "investigate", "read_only": true, "parameters": { From 1830ea0b4d0aea9861cc370de72eb013195e0a47 Mon Sep 17 00:00:00 2001 From: splunk-soar-connectors-admin Date: Wed, 30 Oct 2024 21:54:27 +0000 Subject: [PATCH 15/21] Update README.md --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 64443c1..fd066a8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Product Name: Talos Cloud Intelligence Product Version Supported (regex): ".\*" Minimum Product Version: 6.2.1.305 -This app provides investigative actions for Cisco Talos Intelligence +This app provides investigative actions for Cisco Talos Cloud Intelligence [comment]: # " File: README.md" [comment]: # "Copyright (c) 2024 Splunk Inc." @@ -42,11 +42,12 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION **base_url** | required | string | Base URL provided by Talos **certificate** | optional | password | Certificate contents to authenticate with Talos **key** | optional | password | Private key to authenticate with Talos +**verify_server_cert** | optional | boolean | Verify server certificate ### Supported Actions [test connectivity](#action-test-connectivity) - Validate the asset configuration for connectivity using supplied configuration -[ip reputation](#action-ip-reputation) - Queries IP info -[domain reputation](#action-domain-reputation) - Queries domain info +[ip reputation](#action-ip-reputation) - Query IP info +[domain reputation](#action-domain-reputation) - Query domain info [url reputation](#action-url-reputation) - Queries URL info ## action: 'test connectivity' @@ -62,11 +63,13 @@ No parameters are required for this action No Output ## action: 'ip reputation' -Queries IP info +Query IP info Type: **investigate** Read only: **True** +Provide information on an IP address's reputation, enabling you to take proper action against untrusted, and unwanted resources. + #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- @@ -85,11 +88,13 @@ action_result.data.1.Threat Categories | string | | action_result.data.2.Acceptable Use Policy Categories | string | | ## action: 'domain reputation' -Queries domain info +Query domain info Type: **investigate** Read only: **True** +Provide information on a domain's reputation, enabling you to take proper action against untrusted, and unwanted resources. + #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- From 034845ccf0fdbf0126649658332d0257de136131 Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 15:58:17 -0600 Subject: [PATCH 16/21] moar cleanup --- ciscotalosintelligence.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ciscotalosintelligence.json b/ciscotalosintelligence.json index 3a4331c..985cb1d 100644 --- a/ciscotalosintelligence.json +++ b/ciscotalosintelligence.json @@ -7,7 +7,8 @@ "logo": "ciscotalosintelligence.svg", "logo_dark": "ciscotalosintelligence_dark.svg", "product_name": "Talos Cloud Intelligence", - "python_version": "3", "latest_tested_versions": [ + "python_version": "3", + "latest_tested_versions": [ "Cloud, October 30, 2024" ], "product_version_regex": ".*", @@ -56,7 +57,7 @@ "action": "test connectivity", "identifier": "test_connectivity", "description": "Validate the asset configuration for connectivity using supplied configuration", - "verbose": "", + "verbose": "Action uses the URS API to get a list of the AUP categories used to classify website content.", "type": "test", "read_only": true, "parameters": {}, @@ -216,8 +217,8 @@ { "action": "url reputation", "identifier": "url_reputation", - "description": "Queries URL info", - "verbose": "", + "description": "Query URL info", + "verbose": "Provide information on an URL's reputation, enabling you to take proper action against untrusted, and unwanted resources.", "type": "investigate", "read_only": true, "parameters": { From 4f357f501cf802d22a0a42d55a7a765242e718db Mon Sep 17 00:00:00 2001 From: splunk-soar-connectors-admin Date: Wed, 30 Oct 2024 21:58:55 +0000 Subject: [PATCH 17/21] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd066a8..e403afb 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION [test connectivity](#action-test-connectivity) - Validate the asset configuration for connectivity using supplied configuration [ip reputation](#action-ip-reputation) - Query IP info [domain reputation](#action-domain-reputation) - Query domain info -[url reputation](#action-url-reputation) - Queries URL info +[url reputation](#action-url-reputation) - Query URL info ## action: 'test connectivity' Validate the asset configuration for connectivity using supplied configuration @@ -56,6 +56,8 @@ Validate the asset configuration for connectivity using supplied configuration Type: **test** Read only: **True** +Action uses the URS API to get a list of the AUP categories used to classify website content. + #### Action Parameters No parameters are required for this action @@ -113,11 +115,13 @@ action_result.data.1.Threat Categories | string | | action_result.data.2.Acceptable Use Policy Categories | string | | ## action: 'url reputation' -Queries URL info +Query URL info Type: **investigate** Read only: **True** +Provide information on an URL's reputation, enabling you to take proper action against untrusted, and unwanted resources. + #### Action Parameters PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS --------- | -------- | ----------- | ---- | -------- From d646167290e7362ed65d1576e4f9f121cdcd2ab3 Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 16:00:53 -0600 Subject: [PATCH 18/21] fmt --- ciscotalosintelligence_connector.py | 69 +++++++---------------------- 1 file changed, 17 insertions(+), 52 deletions(-) diff --git a/ciscotalosintelligence_connector.py b/ciscotalosintelligence_connector.py index 61deee7..e0db845 100644 --- a/ciscotalosintelligence_connector.py +++ b/ciscotalosintelligence_connector.py @@ -13,6 +13,7 @@ import tempfile import httpx + # Phantom App imports import phantom.app as phantom import requests @@ -46,9 +47,7 @@ def _process_empty_response(self, response, action_result): return RetVal(phantom.APP_SUCCESS, {}) return RetVal( - action_result.set_status( - phantom.APP_ERROR, "Empty response and no information in the header" - ), + action_result.set_status(phantom.APP_ERROR, "Empty response and no information in the header"), None, ) @@ -65,9 +64,7 @@ def _process_html_response(self, response, action_result): except: error_text = "Cannot parse error details" - message = "Status Code: {0}. Data from server:\n{1}\n".format( - status_code, error_text - ) + message = "Status Code: {0}. Data from server:\n{1}\n".format(status_code, error_text) message = message.replace("{", "{{").replace("}", "}}") return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) @@ -90,9 +87,7 @@ def _process_json_response(self, r, action_result): return RetVal(phantom.APP_SUCCESS, resp_json) # You should process the error returned in the json - message = "Error from server. Status Code: {0} Data from server: {1}".format( - r.status_code, r.text.replace("{", "{{").replace("}", "}}") - ) + message = "Error from server. Status Code: {0} Data from server: {1}".format(r.status_code, r.text.replace("{", "{{").replace("}", "}}")) return RetVal(action_result.set_status(phantom.APP_ERROR, message), None) @@ -137,9 +132,7 @@ def _make_rest_call(self, endpoint, action_result, method="get", **kwargs): # Create a URL to connect to url = self._base_url + endpoint - with tempfile.NamedTemporaryFile( - mode="w+", delete=False, suffix="test" - ) as temp_file: + with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix="test") as temp_file: combined_file = ( "-----BEGIN CERTIFICATE-----\n" f"{self._certificate}\n" @@ -201,9 +194,7 @@ def _handle_test_connectivity(self, param): return action_result.set_status(phantom.APP_SUCCESS) def _handle_ip_reputation(self, param): - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) action_result = self.add_action_result(ActionResult(dict(param))) ip = param["ip"] @@ -213,9 +204,7 @@ def _handle_ip_reputation(self, param): big_endian = int(ip_addr) except Exception: - return action_result.set_status( - phantom.APP_ERROR, "Please provide a valid IP Address" - ) + return action_result.set_status(phantom.APP_ERROR, "Please provide a valid IP Address") payload = { "urls": {"endpoint": [{"ipv4_addr": big_endian}]}, @@ -227,9 +216,7 @@ def _handle_ip_reputation(self, param): return action_result.set_status(phantom.APP_SUCCESS) def _handle_domain_reputation(self, param): - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) action_result = self.add_action_result(ActionResult(dict(param))) domain = param["domain"] @@ -241,9 +228,7 @@ def _handle_domain_reputation(self, param): return action_result.set_status(phantom.APP_SUCCESS) def _handle_url_reputation(self, param): - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) action_result = self.add_action_result(ActionResult(dict(param))) url = param["url"] @@ -261,15 +246,11 @@ def _query_reputation(self, action_result, payload): return action_result.get_status() # make rest call - ret_val, response = self._make_rest_call( - ENDPOINT_QUERY_REPUTATION_V3, action_result, method="post", json=payload - ) + ret_val, response = self._make_rest_call(ENDPOINT_QUERY_REPUTATION_V3, action_result, method="post", json=payload) response_taxonomy_map_version = response["taxonomy_map_version"] if response_taxonomy_map_version > self._state["taxonomy_version"]: - taxonomy_ret_val, taxonomy = self._fetch_taxonomy( - action_result, allow_cache=False - ) + taxonomy_ret_val, taxonomy = self._fetch_taxonomy(action_result, allow_cache=False) if phantom.is_fail(ret_val) or "results" not in response: return action_result.get_status() @@ -290,12 +271,8 @@ def _query_reputation(self, action_result, payload): continue category = taxonomy["taxonomies"][tax_id]["name"]["en-us"]["text"] - name = taxonomy["taxonomies"][tax_id]["entries"][entry_id]["name"][ - "en-us" - ]["text"] - description = taxonomy["taxonomies"][tax_id]["entries"][entry_id][ - "description" - ]["en-us"]["text"] + name = taxonomy["taxonomies"][tax_id]["entries"][entry_id]["name"]["en-us"]["text"] + description = taxonomy["taxonomies"][tax_id]["entries"][entry_id]["description"]["en-us"]["text"] if category == "Threat Levels": threat_level = name @@ -308,18 +285,10 @@ def _query_reputation(self, action_result, payload): action_result.add_data({"Threat Level": threat_level}) summary["Threat Categories"] = threat_categories - action_result.add_data( - {"Threat Categories": ", ".join(list(threat_categories.keys()))} - ) + action_result.add_data({"Threat Categories": ", ".join(list(threat_categories.keys()))}) summary["Acceptable Use Policy Categories"] = aup_categories - action_result.add_data( - { - "Acceptable Use Policy Categories": ", ".join( - list(aup_categories.keys()) - ) - } - ) + action_result.add_data({"Acceptable Use Policy Categories": ", ".join(list(aup_categories.keys()))}) def _fetch_taxonomy(self, action_result, allow_cache=True): payload = {"app_info": self._appinfo} @@ -327,9 +296,7 @@ def _fetch_taxonomy(self, action_result, allow_cache=True): if "taxonomy" in self._state and allow_cache: return 1, self._state["taxonomy"] - ret_val, response = self._make_rest_call( - ENDPOINT_QUERY_TAXONOMIES, action_result, method="post", json=payload - ) + ret_val, response = self._make_rest_call(ENDPOINT_QUERY_TAXONOMIES, action_result, method="post", json=payload) taxonomy = response["catalogs"][str(self._catalog_id)] self._state = {"taxonomy": taxonomy, "taxonomy_version": response["version"]} @@ -366,9 +333,7 @@ def initialize(self): config = self.get_config() def insert_newlines(string, every=64): - return "\n".join( - string[i: i + every] for i in range(0, len(string), every) - ) + return "\n".join(string[i : i + every] for i in range(0, len(string), every)) self._base_url = config["base_url"] self._certificate = insert_newlines(config["certificate"]) From 97431e6da70b25a487e5f3d032d509844c1de48f Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 16:02:52 -0600 Subject: [PATCH 19/21] licenses --- ciscotalosintelligence_connector.py | 21 ++++++++++++++++----- ciscotalosintelligence_consts.py | 18 +++++++++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/ciscotalosintelligence_connector.py b/ciscotalosintelligence_connector.py index e0db845..b78b586 100644 --- a/ciscotalosintelligence_connector.py +++ b/ciscotalosintelligence_connector.py @@ -1,8 +1,19 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# ----------------------------------------- -# Phantom sample App Connector python file -# ----------------------------------------- +# File: ciscotalosintelligence_connector.py +# +# Copyright (c) 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software 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. +# +# # Python 3 Compatibility imports from __future__ import print_function, unicode_literals diff --git a/ciscotalosintelligence_consts.py b/ciscotalosintelligence_consts.py index ffc19d2..2252319 100644 --- a/ciscotalosintelligence_consts.py +++ b/ciscotalosintelligence_consts.py @@ -1,6 +1,22 @@ -# Define your constants here +# File: ciscotalosintelligence_consts.py +# +# Copyright (c) 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software 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. +# +# +# Define your constants here ENDPOINT_QUERY_REPUTATION_V3 = "/Talos.Service.URS/QueryReputationV3" ENDPOINT_QUERY_AUP_CAT_MAP = "/Talos.Service.URS/QueryAUPCatMap" ENDPOINT_QUERY_TAXONOMIES = "/Talos.Service.TTS/QueryTaxonomyCatalogs" From c5be5a89148054d6337e31ba103f97f1cb15e235 Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 16:12:09 -0600 Subject: [PATCH 20/21] fix imports --- ciscotalosintelligence_connector.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ciscotalosintelligence_connector.py b/ciscotalosintelligence_connector.py index b78b586..af51aa4 100644 --- a/ciscotalosintelligence_connector.py +++ b/ciscotalosintelligence_connector.py @@ -15,20 +15,17 @@ # # -# Python 3 Compatibility imports -from __future__ import print_function, unicode_literals - import ipaddress import json import os import tempfile import httpx - -# Phantom App imports import phantom.app as phantom import requests from bs4 import BeautifulSoup + +# Phantom App imports from phantom.action_result import ActionResult from phantom.base_connector import BaseConnector From 307984bb8431c89731d2decd7e6f459f73b9c5b3 Mon Sep 17 00:00:00 2001 From: Scott Odle Date: Wed, 30 Oct 2024 16:12:46 -0600 Subject: [PATCH 21/21] fix imports --- ciscotalosintelligence_connector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ciscotalosintelligence_connector.py b/ciscotalosintelligence_connector.py index af51aa4..db3a92e 100644 --- a/ciscotalosintelligence_connector.py +++ b/ciscotalosintelligence_connector.py @@ -21,11 +21,11 @@ import tempfile import httpx + +# Phantom App imports import phantom.app as phantom import requests from bs4 import BeautifulSoup - -# Phantom App imports from phantom.action_result import ActionResult from phantom.base_connector import BaseConnector