From 2ef163f9b94c0eec301d054f7e399433b1e03dc2 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Thu, 13 Jul 2023 18:08:47 +0300 Subject: [PATCH 01/81] init --- .../Integrations/Tenable_io/Tenable_io.py | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 59921418cbad..ae9f36eeddeb 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1117,30 +1117,31 @@ def export_vulnerabilities_command(args: Dict[str, Any]) -> PollResult: def main(): # pragma: no cover if not (ACCESS_KEY and SECRET_KEY): raise DemistoException('Access Key and Secret Key must be provided.') - if demisto.command() == 'test-module': - demisto.results(test_module()) - elif demisto.command() == 'tenable-io-list-scans': - demisto.results(get_scans_command()) - elif demisto.command() == 'tenable-io-launch-scan': - demisto.results(launch_scan_command()) - elif demisto.command() == 'tenable-io-get-scan-report': - demisto.results(get_report_command()) - elif demisto.command() == 'tenable-io-get-vulnerability-details': - demisto.results(get_vulnerability_details_command()) - elif demisto.command() == 'tenable-io-get-vulnerabilities-by-asset': - demisto.results(get_vulnerabilities_by_asset_command()) - elif demisto.command() == 'tenable-io-get-scan-status': - demisto.results(get_scan_status_command()) - elif demisto.command() == 'tenable-io-pause-scan': - demisto.results(pause_scan_command()) - elif demisto.command() == 'tenable-io-resume-scan': - demisto.results(resume_scan_command()) - elif demisto.command() == 'tenable-io-get-asset-details': - return_results(get_asset_details_command()) - elif demisto.command() == 'tenable-io-export-assets': - return_results(export_assets_command(demisto.args())) - elif demisto.command() == 'tenable-io-export-vulnerabilities': - return_results(export_vulnerabilities_command(demisto.args())) + match demisto.command(): + case 'test-module': + demisto.results(test_module()) + case 'tenable-io-list-scans': + demisto.results(get_scans_command()) + case 'tenable-io-launch-scan': + demisto.results(launch_scan_command()) + case 'tenable-io-get-scan-report': + demisto.results(get_report_command()) + case 'tenable-io-get-vulnerability-details': + demisto.results(get_vulnerability_details_command()) + case 'tenable-io-get-vulnerabilities-by-asset': + demisto.results(get_vulnerabilities_by_asset_command()) + case 'tenable-io-get-scan-status': + demisto.results(get_scan_status_command()) + case 'tenable-io-pause-scan': + demisto.results(pause_scan_command()) + case 'tenable-io-resume-scan': + demisto.results(resume_scan_command()) + case 'tenable-io-get-asset-details': + return_results(get_asset_details_command()) + case 'tenable-io-export-assets': + return_results(export_assets_command(demisto.args())) + case 'tenable-io-export-vulnerabilities': + return_results(export_vulnerabilities_command(demisto.args())) if __name__ in ['__main__', 'builtin', 'builtins']: From 0c2cc8fdc6d29530a5430592eb1fce8060a9c924 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Thu, 13 Jul 2023 19:30:51 +0300 Subject: [PATCH 02/81] list-scan-filters init --- .../Integrations/Tenable_io/Tenable_io.py | 102 ++++++++++++++---- 1 file changed, 81 insertions(+), 21 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index ae9f36eeddeb..4fa97da59444 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -143,8 +143,7 @@ SECRET_KEY = demisto.params().get('credentials_secret_key', {}).get('password') or demisto.params()['secret-key'] USER_AGENT_HEADERS_VALUE = 'Integration/1.0 (PAN; Cortex-XSOAR; Build/2.0)' AUTH_HEADERS = {'X-ApiKeys': f"accessKey={ACCESS_KEY}; secretKey={SECRET_KEY}"} -NEW_HEADERS = { - 'X-ApiKeys': f'accessKey={ACCESS_KEY}; secretKey={SECRET_KEY}', +NEW_HEADERS = AUTH_HEADERS | { 'accept': "application/json", 'content-type': "application/json" } @@ -159,7 +158,7 @@ def flatten(d): r = {} # type: ignore - for k, v in d.items(): + for v in d.values(): if isinstance(v, dict): r.update(flatten(v)) d.update(r) @@ -294,14 +293,14 @@ def get_scan_error_message(response, scan_id): def send_scan_request(scan_id="", endpoint="", method='GET', ignore_license_error=False, body=None, **kwargs): if endpoint: endpoint = '/' + endpoint - full_url = "{0}scans/{1!s}{2}".format(BASE_URL, scan_id, endpoint) + full_url = f"{BASE_URL}scans/{scan_id!s}{endpoint}" try: res = requests.request(method, full_url, headers=AUTH_HEADERS, verify=USE_SSL, json=body, params=kwargs) res.raise_for_status() return res.json() except HTTPError: if ignore_license_error and res.status_code in (403, 500): - return + return None err_msg = get_scan_error_message(res, scan_id) if demisto.command() != 'test-module': return_error(err_msg) @@ -318,6 +317,7 @@ def get_scan_info(scans_result_elem): if response: response['info'].update(scans_result_elem) return response['info'] + return None def send_vuln_details_request(plugin_id, date_range=None): @@ -555,10 +555,10 @@ def get_asset_details_command() -> CommandResults: info = send_asset_details_request(asset_id) attrs = send_asset_attributes_request(asset_id) if attrs: - attributes = [] - for attr in attrs.get("attributes", []): - attributes.append({attr.get('name', ''): attr.get('value', '')}) - info["info"]["attributes"] = attributes + info["info"]["attributes"] = [ + {attr.get('name', ''): attr.get('value', '')} + for attr in attrs.get("attributes", []) + ] except DemistoException as e: return_error(f'Failed to include custom attributes. {e}') @@ -595,6 +595,7 @@ def get_vulnerabilities_by_asset_command(): 'Hostname': indicator } return entry + return None def get_scan_status_command(): @@ -757,16 +758,16 @@ def export_assets_build_command_result(chunks_details_list: list[dict]) -> Comma for chunk_details in chunks_details_list: human_readable_to_append = {} if fqdns := chunk_details.get('fqdns'): - human_readable_to_append.update({'DNS NAME (FQDN)': fqdns[0]}) - if tag := chunk_details.get("tags"): - if first_tag := tag[0]: - human_readable_to_append.update({'TAGS': f'{first_tag.get("key")}:{first_tag.get("value")}'}) - if sources := chunk_details.get("sources"): - if first_source := sources[0]: - human_readable_to_append.update({'SOURCE': first_source.get('name')}) - if network_interfaces := chunk_details.get('network_interfaces'): - if first_network_interfaces := network_interfaces[0]: - human_readable_to_append.update({'IPV4 ADDRESS': first_network_interfaces.get('ipv4s')}) + human_readable_to_append['DNS NAME (FQDN)'] = fqdns[0] + if (tag := chunk_details.get("tags")) and (first_tag := tag[0]): + human_readable_to_append['TAGS'] = f'{first_tag.get("key")}:{first_tag.get("value")}' + if (sources := chunk_details.get("sources")) and (first_source := sources[0]): + human_readable_to_append['SOURCE'] = first_source.get('name') + if ( + (network_interfaces := chunk_details.get('network_interfaces')) + and (first_network_interfaces := network_interfaces[0]) + ): + human_readable_to_append['IPV4 ADDRESS'] = first_network_interfaces.get('ipv4s') human_readable_to_append.update( {'ASSET ID': chunk_details.get('id'), 'SYSTEM TYPE': chunk_details.get('system_types'), @@ -1114,9 +1115,62 @@ def export_vulnerabilities_command(args: Dict[str, Any]) -> PollResult: return request_uuid_export_vulnerabilities(args) +def list_scan_filters_request() -> dict: + response = requests.get( + f'{BASE_URL}filters/scans/reports', + headers=NEW_HEADERS, verify=USE_SSL) + + if response.status_code == 429: + raise DemistoException('Too Many Requests') + + return response.json() + + +def scan_filters_human_readable(response_dict: dict) -> str: + return tableToMarkdown( + 'Tenable IO Scan Filters', + list(map(flatten, response_dict.get('filters', []))), + headers=[ + 'name', 'readable_name', 'type', 'regex', + 'readable_regex', 'operators', 'group_name'], + headerTransform={ + 'name': 'Filter name', + 'readable_name': 'Filter Readable name', + 'type': 'Filter Control type', + 'regex': 'Filter regex', + 'readable_regex': 'Readable regex', + 'operators': 'Filter operators', + 'group_name': 'Filter group name' + }.get + ) + + +def list_scan_filters() -> CommandResults: + + response_dict = list_scan_filters_request() + + return CommandResults( + outputs_prefix='TenableIO.ScanFilter', + outputs_key_field='name', + outputs=response_dict, + readable_output=scan_filters_human_readable(response_dict), + ) + + +def get_scan_history(args: dict[str, Any]) -> CommandResults: + pass + + +def export_scan(args: dict[str, Any]) -> CommandResults: + pass + + def main(): # pragma: no cover + if not (ACCESS_KEY and SECRET_KEY): raise DemistoException('Access Key and Secret Key must be provided.') + + args = demisto.args() match demisto.command(): case 'test-module': demisto.results(test_module()) @@ -1139,9 +1193,15 @@ def main(): # pragma: no cover case 'tenable-io-get-asset-details': return_results(get_asset_details_command()) case 'tenable-io-export-assets': - return_results(export_assets_command(demisto.args())) + return_results(export_assets_command(args)) case 'tenable-io-export-vulnerabilities': - return_results(export_vulnerabilities_command(demisto.args())) + return_results(export_vulnerabilities_command(args)) + case 'tenable-io-list-scan-filters': + return_results(list_scan_filters()) + case 'tenable-io-get-scan-history': + return_results(get_scan_history(args)) + case 'tenable-io-export-scan': + return_results(export_scan(args)) if __name__ in ['__main__', 'builtin', 'builtins']: From deb6b26c321d7d0bd7a5141e9281dce2f408608d Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Fri, 14 Jul 2023 00:55:05 +0300 Subject: [PATCH 03/81] list-scan-history init --- .../Integrations/Tenable_io/Tenable_io.py | 132 ++++++++++++++---- 1 file changed, 105 insertions(+), 27 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 4fa97da59444..b55c275af57f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1115,53 +1115,131 @@ def export_vulnerabilities_command(args: Dict[str, Any]) -> PollResult: return request_uuid_export_vulnerabilities(args) +def handle_errors(response: requests.models.Response): + + if (code := response.status_code) not in range(200, 300): + code_messages = { + 400: + 'Possible reasons:\n' + ' - Your request message is missing a required parameter\n.' + ' - The "format" command argument specified an unsupported' + ' export format for scan results that are older than 60 days.' + ' Only Nessus and CSV formats are supported for scan results that are older than 60 days.' + ' - Your command arguments included filter query parameters for scan results that are older than 60 days.', + 404: 'Tenable Vulnerability Management cannot find the specified scan.', + 492: 'Too Many Requests', + } + raise DemistoException( + f'Error processing request. Got response status code: {code}' + ( + f' - {code_messages[code]}' + if code in code_messages + else f'. Full Response:\n{response.text}')) + + def list_scan_filters_request() -> dict: response = requests.get( f'{BASE_URL}filters/scans/reports', headers=NEW_HEADERS, verify=USE_SSL) - - if response.status_code == 429: - raise DemistoException('Too Many Requests') - + handle_errors(response) return response.json() -def scan_filters_human_readable(response_dict: dict) -> str: +def scan_filters_human_readable(filters: list) -> str: + context_to_hr = { + 'name': 'Filter name', + 'readable_name': 'Filter Readable name', + 'type': 'Filter Control type', + 'regex': 'Filter regex', + 'readable_regex': 'Readable regex', + 'operators': 'Filter operators', + 'group_name': 'Filter group name' + } return tableToMarkdown( 'Tenable IO Scan Filters', - list(map(flatten, response_dict.get('filters', []))), - headers=[ - 'name', 'readable_name', 'type', 'regex', - 'readable_regex', 'operators', 'group_name'], - headerTransform={ - 'name': 'Filter name', - 'readable_name': 'Filter Readable name', - 'type': 'Filter Control type', - 'regex': 'Filter regex', - 'readable_regex': 'Readable regex', - 'operators': 'Filter operators', - 'group_name': 'Filter group name' - }.get + list(map(flatten, filters)), + headers=list(context_to_hr.keys()), + headerTransform=context_to_hr.get, ) -def list_scan_filters() -> CommandResults: +def list_scan_filters_command() -> CommandResults: response_dict = list_scan_filters_request() + filters = response_dict.get('filters', []) return CommandResults( outputs_prefix='TenableIO.ScanFilter', outputs_key_field='name', - outputs=response_dict, - readable_output=scan_filters_human_readable(response_dict), + outputs=filters, + readable_output=scan_filters_human_readable(filters), + raw_response=response_dict, ) -def get_scan_history(args: dict[str, Any]) -> CommandResults: - pass +def get_scan_history_request(scan_id, params) -> dict: + remove_nulls_from_dictionary(params) + response = requests.get( + f'/scans/{scan_id}/history', + params=params, + headers=NEW_HEADERS, + verify=USE_SSL + ) + handle_errors(response) + return response.json() + + +def scan_history_human_readable(history: list) -> str: + context_to_hr = { + 'id': 'History id', + 'scan_uuid': 'History uuid', + 'status': 'Status', + 'is_archived': 'Is archived', + 'custom': 'Targets custom', + 'default': 'Targets default', + 'visibility': 'Visibility', + 'time_start': 'Time start', + 'time_end': 'Time end', + } + return tableToMarkdown( + 'Tenable IO Scan History', + list(map(flatten, history)), + headers=list(context_to_hr.keys()), + headerTransform=context_to_hr.get, + ) + + +def scan_history_params(args: dict) -> dict: + return { + 'sort': + '%2C'.join( + f'{field}%3A{args["sortOrder"]}' # check if theres a default + for field in argToList(args.get('sortFields'))), + 'exclude_rollover': args['excludeRollover'], + 'limit': args['limit'], # unknown if this is the actual name of the param + 'offset': args['offset'], # unknown if this is an arg + } + # 'page_size': args.get('pageSize'), + # 'page': args.get('page'), + + +def get_scan_history_command(args: dict[str, Any]) -> CommandResults: + + response_dict = get_scan_history_request( + args['scanId'], + scan_history_params(args)) + history = response_dict.get('history', []) + + return CommandResults( + outputs_prefix='TenableIO.ScanHistory', + outputs_key_field='id', + outputs=history, + readable_output=scan_history_human_readable(history), + raw_response=response_dict, + ) -def export_scan(args: dict[str, Any]) -> CommandResults: +@polling_function +def export_scan_command(args: dict[str, Any]) -> CommandResults: pass @@ -1197,11 +1275,11 @@ def main(): # pragma: no cover case 'tenable-io-export-vulnerabilities': return_results(export_vulnerabilities_command(args)) case 'tenable-io-list-scan-filters': - return_results(list_scan_filters()) + return_results(list_scan_filters_command()) case 'tenable-io-get-scan-history': - return_results(get_scan_history(args)) + return_results(get_scan_history_command(args)) case 'tenable-io-export-scan': - return_results(export_scan(args)) + return_results(export_scan_command(args)) if __name__ in ['__main__', 'builtin', 'builtins']: From 1646ac5100da543ed4fbcd30b1b8d1a1a6c23acf Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Fri, 14 Jul 2023 01:44:39 +0300 Subject: [PATCH 04/81] export-scan init --- .../Integrations/Tenable_io/Tenable_io.py | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index b55c275af57f..40763d4f01b5 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1179,7 +1179,7 @@ def list_scan_filters_command() -> CommandResults: def get_scan_history_request(scan_id, params) -> dict: remove_nulls_from_dictionary(params) response = requests.get( - f'/scans/{scan_id}/history', + f'{BASE_URL}/scans/{scan_id}/history', params=params, headers=NEW_HEADERS, verify=USE_SSL @@ -1238,11 +1238,44 @@ def get_scan_history_command(args: dict[str, Any]) -> CommandResults: ) -@polling_function -def export_scan_command(args: dict[str, Any]) -> CommandResults: +def initiate_export_scan(scan_id: str, args: dict) -> str: pass +def check_export_scan_status(scan_id: str, file_id: str) -> str: + response = requests.get( + f'{BASE_URL}scans/{scan_id}/export', + headers=NEW_HEADERS, verify=USE_SSL) + handle_errors(response) + return response.json().get('status', '') + + +def download_export_scan(scan_id: str, file_id: str): + pass + + +@polling_function( + 'tenable-io-export-scan', + poll_message='Preparing scan report:', + requires_polling_arg=False, +) +def export_scan_command(args: dict[str, Any]) -> PollResult: + scan_id = args['scanId'] + file_id = args.get('file_id') or initiate_export_scan(scan_id, args) + + match check_export_scan_status(scan_id, file_id): + case 'ready': + download_export_scan(scan_id, file_id) + case 'error': + pass + case 'loading': + pass + case default: + pass + + return PollResult(None, None, None, None) + + def main(): # pragma: no cover if not (ACCESS_KEY and SECRET_KEY): From 2a6caa3467b151910f5f56572e28e184bfd599ee Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sat, 15 Jul 2023 23:29:48 +0300 Subject: [PATCH 05/81] added yml for list-filters --- .../Integrations/Tenable_io/Tenable_io.py | 131 ++++++++++++------ .../Integrations/Tenable_io/Tenable_io.yml | 28 ++++ .../Tenable_io/Tenable_io_test.py | 33 +++++ 3 files changed, 152 insertions(+), 40 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 40763d4f01b5..fb00bedbee1c 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -147,6 +147,7 @@ 'accept': "application/json", 'content-type': "application/json" } +HEADERS = NEW_HEADERS | {'User-Agent': USER_AGENT_HEADERS_VALUE} USE_SSL = not demisto.params()['unsecure'] if not demisto.params()['proxy']: @@ -1115,33 +1116,39 @@ def export_vulnerabilities_command(args: Dict[str, Any]) -> PollResult: return request_uuid_export_vulnerabilities(args) -def handle_errors(response: requests.models.Response): +def safe_get_json(response: requests.models.Response) -> dict: if (code := response.status_code) not in range(200, 300): + demisto.debug(response.text) code_messages = { 400: 'Possible reasons:\n' ' - Your request message is missing a required parameter\n.' ' - The "format" command argument specified an unsupported' - ' export format for scan results that are older than 60 days.' - ' Only Nessus and CSV formats are supported for scan results that are older than 60 days.' + ' export format for scan results that are older than 60 days.\n' + ' Only Nessus and CSV formats are supported for scan results that are older than 60 days.' ' - Your command arguments included filter query parameters for scan results that are older than 60 days.', 404: 'Tenable Vulnerability Management cannot find the specified scan.', - 492: 'Too Many Requests', - } + 492: 'Too Many Requests'} raise DemistoException( f'Error processing request. Got response status code: {code}' + ( f' - {code_messages[code]}' if code in code_messages else f'. Full Response:\n{response.text}')) + try: + return response.json() + except requests.exceptions.JSONDecodeError as e: + demisto.debug(str(e)) + raise DemistoException( + 'Error processing request. ' + f'Unexpected response from Tenable IO:\n{response.text}') def list_scan_filters_request() -> dict: response = requests.get( f'{BASE_URL}filters/scans/reports', - headers=NEW_HEADERS, verify=USE_SSL) - handle_errors(response) - return response.json() + headers=HEADERS, verify=USE_SSL) + return safe_get_json(response) def scan_filters_human_readable(filters: list) -> str: @@ -1152,14 +1159,12 @@ def scan_filters_human_readable(filters: list) -> str: 'regex': 'Filter regex', 'readable_regex': 'Readable regex', 'operators': 'Filter operators', - 'group_name': 'Filter group name' - } + 'group_name': 'Filter group name'} return tableToMarkdown( 'Tenable IO Scan Filters', list(map(flatten, filters)), headers=list(context_to_hr.keys()), - headerTransform=context_to_hr.get, - ) + headerTransform=context_to_hr.get) def list_scan_filters_command() -> CommandResults: @@ -1172,8 +1177,7 @@ def list_scan_filters_command() -> CommandResults: outputs_key_field='name', outputs=filters, readable_output=scan_filters_human_readable(filters), - raw_response=response_dict, - ) + raw_response=response_dict) def get_scan_history_request(scan_id, params) -> dict: @@ -1181,11 +1185,9 @@ def get_scan_history_request(scan_id, params) -> dict: response = requests.get( f'{BASE_URL}/scans/{scan_id}/history', params=params, - headers=NEW_HEADERS, - verify=USE_SSL - ) - handle_errors(response) - return response.json() + headers=HEADERS, + verify=USE_SSL) + return safe_get_json(response) def scan_history_human_readable(history: list) -> str: @@ -1210,10 +1212,9 @@ def scan_history_human_readable(history: list) -> str: def scan_history_params(args: dict) -> dict: return { - 'sort': - '%2C'.join( - f'{field}%3A{args["sortOrder"]}' # check if theres a default - for field in argToList(args.get('sortFields'))), + 'sort': '%2C'.join( + f'{field}%3A{args["sortOrder"]}' # check if theres a default + for field in argToList(args.get('sortFields'))), 'exclude_rollover': args['excludeRollover'], 'limit': args['limit'], # unknown if this is the actual name of the param 'offset': args['offset'], # unknown if this is an arg @@ -1234,46 +1235,96 @@ def get_scan_history_command(args: dict[str, Any]) -> CommandResults: outputs_key_field='id', outputs=history, readable_output=scan_history_human_readable(history), - raw_response=response_dict, - ) + raw_response=response_dict) -def initiate_export_scan(scan_id: str, args: dict) -> str: - pass +def export_scan_request_args( + scanId: str, historyId=None, historyUuid=None, chapters=None, + filterSearchType=None, assetId=None, filterName=None, + filterQuality=None, filterValue=None, **args) -> dict: + + if chapters: + chapters = chapters.replace(',', ';') + elif args['format'] in ('PDF', 'HTML'): + raise DemistoException('The "chapters" field must be provided for PDF or HTML formats.') + + url = f'{BASE_URL}scans/{scanId}/export' + params = { + 'history_id': historyId, + 'history_uuid': historyUuid} + body = { + 'format': args['format'], + 'chapters': chapters, + 'filter.search_type': filterSearchType, + 'asset_id': assetId} + for i, (name, quality, value)\ + in enumerate(zip(*map(argToList, (filterName, filterQuality, filterValue)))): + body |= { + f'filter.{i}.filter': name, + f'filter.{i}.quality': quality, + f'filter.{i}.value': value} + remove_nulls_from_dictionary(params) + remove_nulls_from_dictionary(body) + return {'url': url, 'params': params, 'json': body} + + +def initiate_export_scan(args: dict) -> dict: + response = requests.post( + **export_scan_request_args(**args), + headers=HEADERS, verify=USE_SSL) + return safe_get_json(response) def check_export_scan_status(scan_id: str, file_id: str) -> str: response = requests.get( - f'{BASE_URL}scans/{scan_id}/export', - headers=NEW_HEADERS, verify=USE_SSL) - handle_errors(response) - return response.json().get('status', '') + f'{BASE_URL}scans/{scan_id}/export/{file_id}/status', + headers=HEADERS, verify=USE_SSL) + return safe_get_json(response).get('status', '') def download_export_scan(scan_id: str, file_id: str): + # NEEDS RESEARCH pass @polling_function( 'tenable-io-export-scan', poll_message='Preparing scan report:', - requires_polling_arg=False, -) + requires_polling_arg=False) def export_scan_command(args: dict[str, Any]) -> PollResult: + scan_id = args['scanId'] - file_id = args.get('file_id') or initiate_export_scan(scan_id, args) + if not (file_id := args.get('fileId')): + return PollResult( + CommandResults( + outputs_prefix='InfoFile', + outputs_key_field='file', + outputs=initiate_export_scan(args)), + continue_to_poll=True, + args_for_next_run={ + 'fileId': file_id, 'scanId': scan_id}) match check_export_scan_status(scan_id, file_id): + case 'ready': - download_export_scan(scan_id, file_id) + return PollResult( + download_export_scan(scan_id, file_id), + continue_to_poll=False) + case 'error': - pass + raise DemistoException( + 'Tenable IO encountered an error while exporting the scan report file.\n' + f'Scan ID: {scan_id}\n' + f'File ID: {file_id}\n') + case 'loading': - pass - case default: - pass + return PollResult( + None, + continue_to_poll=True) - return PollResult(None, None, None, None) + case default: + raise DemistoException( + f'Got unexpected status while exporting the scan report file: {default!r}') def main(): # pragma: no cover diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 9358c9a400b7..429c5139330a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1082,6 +1082,34 @@ script: - contextPath: TenableIO.Vulnerability.indexed description: The date and time (in Unix time) when the vulnerability was indexed into Tenable.io. type: Date + - arguments: [] + description: Lists the filtering, sorting, and pagination capabilities available for scan records on endpoints/commands that support them. + name: tenable-io-list-scan-filters + outputs: + - contextPath: TenableIO.ScanFilter.filters.name + description: '' + type: String + - contextPath: TenableIO.ScanFilter.filters.readable_name + description: '' + type: String + - contextPath: TenableIO.ScanFilter.filters.control.type + description: '' + type: String + - contextPath: TenableIO.ScanFilter.filters.control.regex + description: '' + type: String + - contextPath: TenableIO.ScanFilter.filters.control.readable_regex + description: '' + type: String + - contextPath: TenableIO.ScanFilter.filters.operators + description: '' + type: String + - contextPath: TenableIO.ScanFilter.filters.group_name + description: '' + type: Unknown + - contextPath: TenableIO.ScanFilter.filters.control.list + description: '' + type: String runonce: false script: '-' subtype: python3 diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 0a58a0f85c80..4a9edad9fa89 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -417,3 +417,36 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques ' 2023-08-15T15:56:18.852Z | 2023-08-15T15:56:18.852Z |' \ ' some_description | solution. |\n' assert response.raw_response == export_vulnerabilities_response + + +def test_safe_get_json(mocker): + #mock demisto debug + response = requests.models.Response() + response.status_code = 200 + response._content = b'{"key": "value"}' + assert safe_get_json(response) == {'key': 'value'} + + +@pytest.mark.parametrize( + 'status_code, error_message', + ( + (400, 'Got response status code: 400 - Possible reasons:\n'), + (404, 'Got response status code: 404 - Tenable Vulnerability Management cannot find the specified scan.'), + (429, 'Got response status code: 429 - Too Many Requests'), + (500, '. Full Response:\n'), + ) +) +def test_safe_get_json_with_error_codes(mocker, status_code, error_message): + #mock demisto debug + response = requests.models.Response() + response.status_code = status_code + with pytest.raises(DemistoException, match=error_message): + safe_get_json(response) + +def test_safe_get_json_decode_error(mocker): + #mock demisto debug + response = requests.models.Response() + response.status_code = 200 + response._content = b'blabla' + with pytest.raises(DemistoException, match='Error processing request.'): + safe_get_json(response) \ No newline at end of file From cb7e4ae8646ff4839756af3d305318f27e862eaf Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 16 Jul 2023 07:58:09 +0300 Subject: [PATCH 06/81] refactoring --- .../Integrations/Tenable_io/Tenable_io.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index fb00bedbee1c..c3134320ec16 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1268,11 +1268,19 @@ def export_scan_request_args( return {'url': url, 'params': params, 'json': body} -def initiate_export_scan(args: dict) -> dict: +def initiate_export_scan(scan_id: str, args: dict) -> PollResult: response = requests.post( **export_scan_request_args(**args), headers=HEADERS, verify=USE_SSL) - return safe_get_json(response) + response_json = safe_get_json(response) + return PollResult( + None, True, + {'fileId': response_json['file'], + 'scanId': scan_id}, + CommandResults( + outputs_prefix='InfoFile', + outputs_key_field='file', + outputs=response_json)) def check_export_scan_status(scan_id: str, file_id: str) -> str: @@ -1293,19 +1301,12 @@ def download_export_scan(scan_id: str, file_id: str): requires_polling_arg=False) def export_scan_command(args: dict[str, Any]) -> PollResult: - scan_id = args['scanId'] - if not (file_id := args.get('fileId')): - return PollResult( - CommandResults( - outputs_prefix='InfoFile', - outputs_key_field='file', - outputs=initiate_export_scan(args)), - continue_to_poll=True, - args_for_next_run={ - 'fileId': file_id, 'scanId': scan_id}) + scan_id, file_id = args['scanId'], args.get('fileId') - match check_export_scan_status(scan_id, file_id): + if not file_id: + return initiate_export_scan(scan_id, args) + match check_export_scan_status(scan_id, file_id): case 'ready': return PollResult( download_export_scan(scan_id, file_id), @@ -1319,8 +1320,7 @@ def export_scan_command(args: dict[str, Any]) -> PollResult: case 'loading': return PollResult( - None, - continue_to_poll=True) + None, continue_to_poll=True) case default: raise DemistoException( From 8087864dee0b689edf463e9ac6ac0a80c74de12f Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 16 Jul 2023 19:19:17 +0300 Subject: [PATCH 07/81] yml version 1.0 --- .../Integrations/Tenable_io/Tenable_io.py | 25 ++-- .../Integrations/Tenable_io/Tenable_io.yml | 129 ++++++++++++++++-- 2 files changed, 134 insertions(+), 20 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index c3134320ec16..3252be318b47 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1213,14 +1213,11 @@ def scan_history_human_readable(history: list) -> str: def scan_history_params(args: dict) -> dict: return { 'sort': '%2C'.join( - f'{field}%3A{args["sortOrder"]}' # check if theres a default + f'{field}%3A{args["sortOrder"]}' for field in argToList(args.get('sortFields'))), 'exclude_rollover': args['excludeRollover'], - 'limit': args['limit'], # unknown if this is the actual name of the param - 'offset': args['offset'], # unknown if this is an arg - } - # 'page_size': args.get('pageSize'), - # 'page': args.get('page'), + 'limit': args.get('pageSize') or args['limit'], + 'offset': int(args.get('pageSize', 0)) * int(args.get('page', 0))} def get_scan_history_command(args: dict[str, Any]) -> CommandResults: @@ -1244,7 +1241,7 @@ def export_scan_request_args( filterQuality=None, filterValue=None, **args) -> dict: if chapters: - chapters = chapters.replace(',', ';') + chapters = ';'.join(argToList(chapters)) elif args['format'] in ('PDF', 'HTML'): raise DemistoException('The "chapters" field must be provided for PDF or HTML formats.') @@ -1253,7 +1250,7 @@ def export_scan_request_args( 'history_id': historyId, 'history_uuid': historyUuid} body = { - 'format': args['format'], + 'format': args['format'].lower(), 'chapters': chapters, 'filter.search_type': filterSearchType, 'asset_id': assetId} @@ -1290,9 +1287,13 @@ def check_export_scan_status(scan_id: str, file_id: str) -> str: return safe_get_json(response).get('status', '') -def download_export_scan(scan_id: str, file_id: str): - # NEEDS RESEARCH - pass +def download_export_scan(scan_id: str, file_id: str, args: dict) -> dict: + data = requests.get( + f'{BASE_URL}scans/{scan_id}/export/{file_id}/download', + headers=HEADERS, verify=USE_SSL) + return fileResult( + f'scan_{scan_id}_{file_id}.{args["format"].lower()}', + data, EntryType.ENTRY_INFO_FILE) @polling_function( @@ -1309,7 +1310,7 @@ def export_scan_command(args: dict[str, Any]) -> PollResult: match check_export_scan_status(scan_id, file_id): case 'ready': return PollResult( - download_export_scan(scan_id, file_id), + download_export_scan(scan_id, file_id, args), continue_to_poll=False) case 'error': diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 429c5139330a..c7fbe0aff437 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1086,28 +1086,141 @@ script: description: Lists the filtering, sorting, and pagination capabilities available for scan records on endpoints/commands that support them. name: tenable-io-list-scan-filters outputs: - - contextPath: TenableIO.ScanFilter.filters.name + - contextPath: TenableIO.ScanFilter.name description: '' type: String - - contextPath: TenableIO.ScanFilter.filters.readable_name + - contextPath: TenableIO.ScanFilter.readable_name description: '' type: String - - contextPath: TenableIO.ScanFilter.filters.control.type + - contextPath: TenableIO.ScanFilter.control.type description: '' type: String - - contextPath: TenableIO.ScanFilter.filters.control.regex + - contextPath: TenableIO.ScanFilter.control.regex description: '' type: String - - contextPath: TenableIO.ScanFilter.filters.control.readable_regex + - contextPath: TenableIO.ScanFilter.control.readable_regex description: '' type: String - - contextPath: TenableIO.ScanFilter.filters.operators + - contextPath: TenableIO.ScanFilter.operators description: '' type: String - - contextPath: TenableIO.ScanFilter.filters.group_name + - contextPath: TenableIO.ScanFilter.group_name description: '' type: Unknown - - contextPath: TenableIO.ScanFilter.filters.control.list + - contextPath: TenableIO.ScanFilter.control.list + description: '' + type: String + - arguments: + - name: scanId + description: The ID of the scan to get the runs of. + required: true + - name: sortFields + description: 'The fields by which to sort, in the order defined by "sortOrder". Possible values: start_date, end_date, status.' + isArray: true + - name: sortOrder + description: The direction in which to sort the fields defined by "sortFields". + defaultValue: asc + auto: PREDEFINED + predefined: + - asc + - desc + - name: excludeRollover + description: Whether or not to exclude rollover scans from the scan history. + defaultValue: 'false' + auto: PREDEFINED + predefined: + - 'true' + - 'false' + - name: page + description: The page number of scan records to retrieve (used for pagination). The page size is defined by the "pageSize" argument. + - name: pageSize + description: The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. + - name: limit + description: The number of records to retrieve. If "pageSize" is defined, this argument is ignored. + defaultValue: '50' + description: Lists the individual runs of the specified scan. + name: tenable-io-get-scan-history + outputs: + - contextPath: TenableIO.ScanHistory.time_end + description: '' + type: Number + - contextPath: TenableIO.ScanHistory.scan_uuid + description: '' + type: String + - contextPath: TenableIO.ScanHistory.id + description: '' + type: Number + - contextPath: TenableIO.ScanHistory.is_archived + description: '' + type: Boolean + - contextPath: TenableIO.ScanHistory.time_start + description: '' + type: Number + - contextPath: TenableIO.ScanHistory.visibility + description: '' + type: String + - contextPath: TenableIO.ScanHistory.targets.custom + description: '' + type: Boolean + - contextPath: TenableIO.ScanHistory.targets.default + description: '' + type: Unknown + - contextPath: TenableIO.ScanHistory.status + description: '' + type: String + - arguments: + - name: scanId + description: The identifier for the scan to export. Run the "tenable-io-list-scans" command to get all available scans. + required: true + - name: historyId + description: The unique identifier of the historical data to export. Run the "tenable-io-get-scan-history" command to get history IDs. + - name: historyUuid + description: The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. + - name: format + description: The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 60 days. For scans that are older than 60 days, only the Nessus and CSV formats are supported. + required: true + auto: PREDEFINED + predefined: + - Nessus + - HTML + - PDF + - CSV + - name: chapters + description: | + A list of chapters to include in the export. + Chapters can be any of the following options: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. + This argument is required if the file format is PDF or HTML. + isArray: true + - name: filterName + description: | + A list of filters to apply to the exported scan report. + Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response). + The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to make individual filters. + isArray: true + - name: filterQuality + description: | + A list of operators for the filter to apply to the exported scan report. + Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response). + The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to make individual filters. + isArray: true + - name: filterValue + description: | + A list of values for the filter to apply to the exported scan report. + Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response). + The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to make individual filters. + isArray: true + - name: filterSearchType + description: For multiple filters, specifies whether to use the AND or the OR logical operator. + - name: assetId + description: The ID of the asset scanned. + polling: true + description: Export and download a scan report. + name: tenable-io-export-scan + outputs: + - contextPath: InfoFile.file + description: '' + # type: Number + - contextPath: InfoFile.temp_token description: '' type: String runonce: false From 94ab4b347d721cfc8092e39918b820c0c8558c72 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 17 Jul 2023 11:03:52 +0300 Subject: [PATCH 08/81] fixed sortField bug --- .../Integrations/Tenable_io/Tenable_io.py | 28 ++++++++++++------- .../Integrations/Tenable_io/Tenable_io.yml | 25 ++++++++++------- .../Tenable_io/command_examples.txt | 3 ++ 3 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 3252be318b47..f3acdc3720e5 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -966,7 +966,7 @@ def export_assets_command(args: Dict[str, Any]) -> PollResult: chunks_details_list = get_export_chunks_details(export_uuid_status_response, export_uuid, 'assets') command_results = export_assets_build_command_result(chunks_details_list) return PollResult(command_results) - elif status == 'PROCESSING' or status == 'QUEUED': + elif status in ('PROCESSING', 'QUEUED'): return PollResult( response=None, partial_result=CommandResults( @@ -1094,7 +1094,7 @@ def export_vulnerabilities_command(args: Dict[str, Any]) -> PollResult: chunks_details_list = get_export_chunks_details(export_uuid_status_response, export_uuid, 'vulns') command_results = export_vulnerabilities_build_command_result(chunks_details_list) return PollResult(command_results) - elif status == 'PROCESSING' or status == 'QUEUED': + elif status in ('PROCESSING', 'QUEUED'): return PollResult( response=None, partial_result=CommandResults( @@ -1141,7 +1141,8 @@ def safe_get_json(response: requests.models.Response) -> dict: demisto.debug(str(e)) raise DemistoException( 'Error processing request. ' - f'Unexpected response from Tenable IO:\n{response.text}') + 'Unexpected response from Tenable IO:' + f'\n{response.text}') from e def list_scan_filters_request() -> dict: @@ -1183,7 +1184,7 @@ def list_scan_filters_command() -> CommandResults: def get_scan_history_request(scan_id, params) -> dict: remove_nulls_from_dictionary(params) response = requests.get( - f'{BASE_URL}/scans/{scan_id}/history', + f'{BASE_URL}scans/{scan_id}/history', params=params, headers=HEADERS, verify=USE_SSL) @@ -1212,8 +1213,8 @@ def scan_history_human_readable(history: list) -> str: def scan_history_params(args: dict) -> dict: return { - 'sort': '%2C'.join( - f'{field}%3A{args["sortOrder"]}' + 'sort': ','.join( + f'{field}:{args["sortOrder"]}' for field in argToList(args.get('sortFields'))), 'exclude_rollover': args['excludeRollover'], 'limit': args.get('pageSize') or args['limit'], @@ -1270,10 +1271,16 @@ def initiate_export_scan(scan_id: str, args: dict) -> PollResult: **export_scan_request_args(**args), headers=HEADERS, verify=USE_SSL) response_json = safe_get_json(response) + + if not (file_id := response_json.get('file')): + raise DemistoException( + f'Unexpected response from Tenable IO: {response_json}') + return PollResult( None, True, - {'fileId': response_json['file'], - 'scanId': scan_id}, + {'fileId': file_id, + 'scanId': scan_id, + 'format': args['format']}, CommandResults( outputs_prefix='InfoFile', outputs_key_field='file', @@ -1288,17 +1295,18 @@ def check_export_scan_status(scan_id: str, file_id: str) -> str: def download_export_scan(scan_id: str, file_id: str, args: dict) -> dict: - data = requests.get( + response = requests.get( f'{BASE_URL}scans/{scan_id}/export/{file_id}/download', headers=HEADERS, verify=USE_SSL) return fileResult( f'scan_{scan_id}_{file_id}.{args["format"].lower()}', - data, EntryType.ENTRY_INFO_FILE) + response.content, EntryType.ENTRY_INFO_FILE) @polling_function( 'tenable-io-export-scan', poll_message='Preparing scan report:', + interval=15, requires_polling_arg=False) def export_scan_command(args: dict[str, Any]) -> PollResult: diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index c7fbe0aff437..d1a551b2447a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1112,20 +1112,20 @@ script: type: String - arguments: - name: scanId - description: The ID of the scan to get the runs of. + description: The ID of the scan of which to get the runs. required: true - name: sortFields description: 'The fields by which to sort, in the order defined by "sortOrder". Possible values: start_date, end_date, status.' isArray: true - name: sortOrder - description: The direction in which to sort the fields defined by "sortFields". + description: The direction in which to sort the fields defined by "sortFields". Default is "asc". defaultValue: asc auto: PREDEFINED predefined: - asc - desc - name: excludeRollover - description: Whether or not to exclude rollover scans from the scan history. + description: Whether or not to exclude rollover scans from the scan history. Default is false. defaultValue: 'false' auto: PREDEFINED predefined: @@ -1177,8 +1177,11 @@ script: - name: historyUuid description: The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. - name: format - description: The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 60 days. For scans that are older than 60 days, only the Nessus and CSV formats are supported. + description: | + The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 60 days. + For scans that are older than 60 days, only the Nessus and CSV formats are supported. Default is CSV. required: true + defaultValue: CSV auto: PREDEFINED predefined: - Nessus @@ -1195,32 +1198,34 @@ script: description: | A list of filters to apply to the exported scan report. Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response). - The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to make individual filters. + The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. isArray: true - name: filterQuality description: | A list of operators for the filter to apply to the exported scan report. Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response). - The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to make individual filters. + The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. isArray: true - name: filterValue description: | A list of values for the filter to apply to the exported scan report. Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response). - The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to make individual filters. + The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. isArray: true - name: filterSearchType description: For multiple filters, specifies whether to use the AND or the OR logical operator. - name: assetId description: The ID of the asset scanned. + - name: fileId + hidden: true + - name: hide_polling_output + defaultValue: true + hidden: true polling: true description: Export and download a scan report. name: tenable-io-export-scan outputs: - contextPath: InfoFile.file - description: '' - # type: Number - - contextPath: InfoFile.temp_token description: '' type: String runonce: false diff --git a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt new file mode 100644 index 000000000000..e066d87b4d70 --- /dev/null +++ b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt @@ -0,0 +1,3 @@ +!tenable-io-list-scan-filters +!tenable-io-get-scan-history scanId=16 excludeRollover=true sortFields=end_date,status sortOrder=desc page=2 pageSize=30 +!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" \ No newline at end of file From 3bb8e9cf0874923f65fd539a9105c48c8059a74b Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 17 Jul 2023 13:34:48 +0300 Subject: [PATCH 09/81] added defaultValue for filterSearchType --- .../Integrations/Tenable_io/Tenable_io.py | 23 +++--- .../Integrations/Tenable_io/Tenable_io.yml | 5 ++ .../Tenable_io/Tenable_io_test.py | 79 +++++++++++-------- .../Tenable_io/command_examples.txt | 2 +- 4 files changed, 68 insertions(+), 41 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index f3acdc3720e5..4015c0bf3a22 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1129,7 +1129,8 @@ def safe_get_json(response: requests.models.Response) -> dict: ' Only Nessus and CSV formats are supported for scan results that are older than 60 days.' ' - Your command arguments included filter query parameters for scan results that are older than 60 days.', 404: 'Tenable Vulnerability Management cannot find the specified scan.', - 492: 'Too Many Requests'} + 492: 'Too Many Requests', + } raise DemistoException( f'Error processing request. Got response status code: {code}' + ( f' - {code_messages[code]}' @@ -1160,7 +1161,8 @@ def scan_filters_human_readable(filters: list) -> str: 'regex': 'Filter regex', 'readable_regex': 'Readable regex', 'operators': 'Filter operators', - 'group_name': 'Filter group name'} + 'group_name': 'Filter group name', + } return tableToMarkdown( 'Tenable IO Scan Filters', list(map(flatten, filters)), @@ -1207,8 +1209,7 @@ def scan_history_human_readable(history: list) -> str: 'Tenable IO Scan History', list(map(flatten, history)), headers=list(context_to_hr.keys()), - headerTransform=context_to_hr.get, - ) + headerTransform=context_to_hr.get) def scan_history_params(args: dict) -> dict: @@ -1218,7 +1219,8 @@ def scan_history_params(args: dict) -> dict: for field in argToList(args.get('sortFields'))), 'exclude_rollover': args['excludeRollover'], 'limit': args.get('pageSize') or args['limit'], - 'offset': int(args.get('pageSize', 0)) * int(args.get('page', 0))} + 'offset': int(args.get('pageSize', 0)) * int(args.get('page', 0)), + } def get_scan_history_command(args: dict[str, Any]) -> CommandResults: @@ -1249,18 +1251,21 @@ def export_scan_request_args( url = f'{BASE_URL}scans/{scanId}/export' params = { 'history_id': historyId, - 'history_uuid': historyUuid} + 'history_uuid': historyUuid, + } body = { 'format': args['format'].lower(), 'chapters': chapters, 'filter.search_type': filterSearchType, - 'asset_id': assetId} + 'asset_id': assetId, + } for i, (name, quality, value)\ in enumerate(zip(*map(argToList, (filterName, filterQuality, filterValue)))): body |= { f'filter.{i}.filter': name, f'filter.{i}.quality': quality, - f'filter.{i}.value': value} + f'filter.{i}.value': value, + } remove_nulls_from_dictionary(params) remove_nulls_from_dictionary(body) return {'url': url, 'params': params, 'json': body} @@ -1280,7 +1285,7 @@ def initiate_export_scan(scan_id: str, args: dict) -> PollResult: None, True, {'fileId': file_id, 'scanId': scan_id, - 'format': args['format']}, + 'format': args['format']}, # not necessary but avoids confusion CommandResults( outputs_prefix='InfoFile', outputs_key_field='file', diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index d1a551b2447a..adea8a51c5f6 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1214,6 +1214,11 @@ script: isArray: true - name: filterSearchType description: For multiple filters, specifies whether to use the AND or the OR logical operator. + defaultValue: and + auto: PREDEFINED + predefined: + - and + - or - name: assetId description: The ID of the asset scanned. - name: fileId diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 4a9edad9fa89..f95f06d2336f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -2,6 +2,7 @@ import pytest from freezegun import freeze_time from CommonServerPython import * +import json MOCK_PARAMS = { 'access-key': 'fake_access_key', @@ -44,10 +45,16 @@ ] +def load_json(filename): + with open(f'test_data/{filename}.json') as f: + return json.load(f) + + def mock_demisto(mocker, mock_args): mocker.patch.object(demisto, 'params', return_value=MOCK_PARAMS) mocker.patch.object(demisto, 'args', return_value=mock_args) mocker.patch.object(demisto, 'results') + mocker.patch.object(demisto, 'debug') def test_get_scan_status(mocker, requests_mock): @@ -419,34 +426,44 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques assert response.raw_response == export_vulnerabilities_response -def test_safe_get_json(mocker): - #mock demisto debug - response = requests.models.Response() - response.status_code = 200 - response._content = b'{"key": "value"}' - assert safe_get_json(response) == {'key': 'value'} - - -@pytest.mark.parametrize( - 'status_code, error_message', - ( - (400, 'Got response status code: 400 - Possible reasons:\n'), - (404, 'Got response status code: 404 - Tenable Vulnerability Management cannot find the specified scan.'), - (429, 'Got response status code: 429 - Too Many Requests'), - (500, '. Full Response:\n'), - ) -) -def test_safe_get_json_with_error_codes(mocker, status_code, error_message): - #mock demisto debug - response = requests.models.Response() - response.status_code = status_code - with pytest.raises(DemistoException, match=error_message): - safe_get_json(response) - -def test_safe_get_json_decode_error(mocker): - #mock demisto debug - response = requests.models.Response() - response.status_code = 200 - response._content = b'blabla' - with pytest.raises(DemistoException, match='Error processing request.'): - safe_get_json(response) \ No newline at end of file +# def test_safe_get_json(mocker): +# """ +# Given: +# - A response from Tenable IO +# When: +# - Running the "list-scan-filters", "get-scan-history" and "export-scan" commands. +# Then: +# - Verify that tenable-io-export-vulnerabilities command works as expected. +# """ +# response = requests.models.Response() +# response.status_code = 200 +# response._content = b'{"key": "value"}' +# assert safe_get_json(response) == {'key': 'value'} + + +# @pytest.mark.parametrize( +# 'status_code, error_message', +# ( +# (400, 'Got response status code: 400 - Possible reasons:\n'), +# (404, 'Got response status code: 404 - Tenable Vulnerability Management cannot find the specified scan.'), +# (429, 'Got response status code: 429 - Too Many Requests'), +# (500, '. Full Response:\n'), +# ) +# ) +# def test_safe_get_json_with_error_codes(mocker, status_code, error_message): +# #mock demisto debug +# response = requests.models.Response() +# response.status_code = status_code +# with pytest.raises(DemistoException, match=error_message): +# safe_get_json(response) + + +# def test_safe_get_json_decode_error(mocker): +# ''' + +# ''' +# response = requests.models.Response() +# response.status_code = 200 +# response._content = b'blabla' +# with pytest.raises(DemistoException, match='Error processing request.'): +# safe_get_json(response) \ No newline at end of file diff --git a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt index e066d87b4d70..9b12d9a262c9 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt +++ b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt @@ -1,3 +1,3 @@ !tenable-io-list-scan-filters !tenable-io-get-scan-history scanId=16 excludeRollover=true sortFields=end_date,status sortOrder=desc page=2 pageSize=30 -!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" \ No newline at end of file +!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=or filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue="[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*,(,[0-9]0-9)*" assetId=10 \ No newline at end of file From afe996028fe10d1e83a927304ebf80e86a0621e8 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 17 Jul 2023 13:48:45 +0300 Subject: [PATCH 10/81] update output for export file --- .gitlab/ci/bucket-upload.yml | 2 -- .gitlab/ci/global.yml | 1 - Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 3 ++- Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitlab/ci/bucket-upload.yml b/.gitlab/ci/bucket-upload.yml index 432611e10467..76f9a023faec 100644 --- a/.gitlab/ci/bucket-upload.yml +++ b/.gitlab/ci/bucket-upload.yml @@ -137,7 +137,6 @@ xpanse-prepare-testing-bucket-upload-flow: - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/installations.log - SECRET_CONF_PATH=$(cat secret_conf_path) - python3 ./Tests/scripts/add_secrets_file_to_build.py -sa "$GSM_SERVICE_ACCOUNT" -sf "$SECRET_CONF_PATH" -u "$DEMISTO_USERNAME" -p "$DEMISTO_PASSWORD" -gpid "$GSM_PROJECT_ID" - - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/download_conf_repos.log - section_end "Download configuration" - !reference [.ssh-config-setup] - section_start "Get Instance Variables" @@ -209,7 +208,6 @@ install-packs-in-server-master: - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/installations.log - SECRET_CONF_PATH=$(cat secret_conf_path) - python3 ./Tests/scripts/add_secrets_file_to_build.py -sa "$GSM_SERVICE_ACCOUNT" -sf "$SECRET_CONF_PATH" -u "$DEMISTO_USERNAME" -p "$DEMISTO_PASSWORD" -gpid "$GSM_PROJECT_ID" - - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/download_conf_repos.log - section_end "Download configuration" - section_start "Lock Machine" - echo "Authenticating GCP" diff --git a/.gitlab/ci/global.yml b/.gitlab/ci/global.yml index a763b67388c3..45333684647c 100644 --- a/.gitlab/ci/global.yml +++ b/.gitlab/ci/global.yml @@ -57,7 +57,6 @@ - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/download_demisto_conf.log - SECRET_CONF_PATH=$(cat secret_conf_path) - python3 ./Tests/scripts/add_secrets_file_to_build.py -sa "$GSM_SERVICE_ACCOUNT" -sf "$SECRET_CONF_PATH" -u "$DEMISTO_USERNAME" -p "$DEMISTO_PASSWORD" -gpid "$GSM_PROJECT_ID" - - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/download_conf_repos.log - section_end "Download content-test-conf" .ssh-config-setup: diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 4015c0bf3a22..6d9023650ede 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1289,7 +1289,8 @@ def initiate_export_scan(scan_id: str, args: dict) -> PollResult: CommandResults( outputs_prefix='InfoFile', outputs_key_field='file', - outputs=response_json)) + outputs=response_json, + readable_output=f'Exporting File. ID: {file_id}')) def check_export_scan_status(scan_id: str, file_id: str) -> str: diff --git a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt index 9b12d9a262c9..f1f63086bc8a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt +++ b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt @@ -1,3 +1,3 @@ !tenable-io-list-scan-filters !tenable-io-get-scan-history scanId=16 excludeRollover=true sortFields=end_date,status sortOrder=desc page=2 pageSize=30 -!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=or filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue="[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*,(,[0-9]0-9)*" assetId=10 \ No newline at end of file +!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=or filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue=".*,(.[0-9]0-9)*" assetId=10 \ No newline at end of file From a276ec4dd899b7288333c3411d8e61855e4283f9 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 17 Jul 2023 13:54:24 +0300 Subject: [PATCH 11/81] remove gitlab --- .gitlab/ci/bucket-upload.yml | 2 ++ .gitlab/ci/global.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/.gitlab/ci/bucket-upload.yml b/.gitlab/ci/bucket-upload.yml index 76f9a023faec..432611e10467 100644 --- a/.gitlab/ci/bucket-upload.yml +++ b/.gitlab/ci/bucket-upload.yml @@ -137,6 +137,7 @@ xpanse-prepare-testing-bucket-upload-flow: - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/installations.log - SECRET_CONF_PATH=$(cat secret_conf_path) - python3 ./Tests/scripts/add_secrets_file_to_build.py -sa "$GSM_SERVICE_ACCOUNT" -sf "$SECRET_CONF_PATH" -u "$DEMISTO_USERNAME" -p "$DEMISTO_PASSWORD" -gpid "$GSM_PROJECT_ID" + - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/download_conf_repos.log - section_end "Download configuration" - !reference [.ssh-config-setup] - section_start "Get Instance Variables" @@ -208,6 +209,7 @@ install-packs-in-server-master: - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/installations.log - SECRET_CONF_PATH=$(cat secret_conf_path) - python3 ./Tests/scripts/add_secrets_file_to_build.py -sa "$GSM_SERVICE_ACCOUNT" -sf "$SECRET_CONF_PATH" -u "$DEMISTO_USERNAME" -p "$DEMISTO_PASSWORD" -gpid "$GSM_PROJECT_ID" + - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/download_conf_repos.log - section_end "Download configuration" - section_start "Lock Machine" - echo "Authenticating GCP" diff --git a/.gitlab/ci/global.yml b/.gitlab/ci/global.yml index 45333684647c..a763b67388c3 100644 --- a/.gitlab/ci/global.yml +++ b/.gitlab/ci/global.yml @@ -57,6 +57,7 @@ - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/download_demisto_conf.log - SECRET_CONF_PATH=$(cat secret_conf_path) - python3 ./Tests/scripts/add_secrets_file_to_build.py -sa "$GSM_SERVICE_ACCOUNT" -sf "$SECRET_CONF_PATH" -u "$DEMISTO_USERNAME" -p "$DEMISTO_PASSWORD" -gpid "$GSM_PROJECT_ID" + - ./Tests/scripts/download_conf_repos.sh >> $ARTIFACTS_FOLDER/logs/download_conf_repos.log - section_end "Download content-test-conf" .ssh-config-setup: From 9534f3ae3aeb38b9c2e615c0cd050844e37a4ff6 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 17 Jul 2023 15:40:26 +0300 Subject: [PATCH 12/81] improved yml --- .../Integrations/Tenable_io/Tenable_io.py | 16 ++++++++++------ .../Integrations/Tenable_io/Tenable_io.yml | 13 +++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 6d9023650ede..f6c8e2091366 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -299,7 +299,8 @@ def send_scan_request(scan_id="", endpoint="", method='GET', ignore_license_erro res = requests.request(method, full_url, headers=AUTH_HEADERS, verify=USE_SSL, json=body, params=kwargs) res.raise_for_status() return res.json() - except HTTPError: + except HTTPError as e: + demisto.debug(str(e)) if ignore_license_error and res.status_code in (403, 500): return None err_msg = get_scan_error_message(res, scan_id) @@ -1118,8 +1119,11 @@ def export_vulnerabilities_command(args: Dict[str, Any]) -> PollResult: def safe_get_json(response: requests.models.Response) -> dict: - if (code := response.status_code) not in range(200, 300): - demisto.debug(response.text) + try: + response.raise_for_status() + return response.json() + except HTTPError as e: + demisto.debug(f'Response: {response.text}\nError: {e}') code_messages = { 400: 'Possible reasons:\n' @@ -1131,13 +1135,13 @@ def safe_get_json(response: requests.models.Response) -> dict: 404: 'Tenable Vulnerability Management cannot find the specified scan.', 492: 'Too Many Requests', } + code = response.status_code raise DemistoException( f'Error processing request. Got response status code: {code}' + ( f' - {code_messages[code]}' if code in code_messages - else f'. Full Response:\n{response.text}')) - try: - return response.json() + else f'. Full Response:\n{response.text}') + ) from e except requests.exceptions.JSONDecodeError as e: demisto.debug(str(e)) raise DemistoException( diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index be06290b3b40..a946b89c97c2 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1123,6 +1123,11 @@ script: - name: sortFields description: 'The fields by which to sort, in the order defined by "sortOrder". Possible values: start_date, end_date, status.' isArray: true + auto: PREDEFINED + predefined: + - start_date + - end_date + - status - name: sortOrder description: The direction in which to sort the fields defined by "sortFields". Default is "asc". defaultValue: asc @@ -1200,6 +1205,14 @@ script: Chapters can be any of the following options: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. This argument is required if the file format is PDF or HTML. isArray: true + auto: PREDEFINED + predefined: + - vuln_hosts_summary + - vuln_by_host + - compliance_exec + - remediations + - vuln_by_plugin + - compliance - name: filterName description: | A list of filters to apply to the exported scan report. From f54ec3cddfa6c01aae01baf523c65078a92ed793 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 17 Jul 2023 16:57:12 +0300 Subject: [PATCH 13/81] added test playbook --- ...ok-Tenable.io_Run_And_Export_Scan_test.yml | 279 ++++++++++++++++++ .../playbook-Tenable.io_test.yml | 208 +++++++------ Utils/tests/id_set.json | 16 + 3 files changed, 418 insertions(+), 85 deletions(-) create mode 100644 Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml new file mode 100644 index 000000000000..3fa51e013f7b --- /dev/null +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml @@ -0,0 +1,279 @@ +id: Tenable.io Run And Export Scan Test +version: -1 +name: Tenable.io Run And Export Scan Test +starttaskid: "0" +tasks: + "0": + id: "0" + taskid: 3e71dac7-f071-4cce-8fd8-ea8d45cfa36e + type: start + task: + id: 3e71dac7-f071-4cce-8fd8-ea8d45cfa36e + version: -1 + name: "" + iscommand: false + brand: "" + nexttasks: + '#none#': + - "1" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 265, + "y": 50 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "1": + id: "1" + taskid: f3496d83-f168-44f0-81b0-ed92c9daf7b7 + type: playbook + task: + id: f3496d83-f168-44f0-81b0-ed92c9daf7b7 + version: -1 + name: Tenable.io Scan + playbookName: Tenable.io Scan + type: playbook + iscommand: false + brand: "" + nexttasks: + '#none#': + - "2" + - "3" + scriptarguments: + scan-id: + simple: "16" + separatecontext: true + continueonerrortype: "" + loop: + iscommand: false + exitCondition: "" + wait: 1 + max: 100 + view: |- + { + "position": { + "x": 265, + "y": 195 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "2": + id: "2" + taskid: 64853254-3f54-4a24-8195-0ae5c3a46ce8 + type: regular + task: + id: 64853254-3f54-4a24-8195-0ae5c3a46ce8 + version: -1 + name: Run get-scan-history Command + description: Lists the individual runs of the specified scan. + script: Tenable.io|||tenable-io-get-scan-history + type: regular + iscommand: true + brand: Tenable.io + nexttasks: + '#none#': + - "4" + scriptarguments: + excludeRollover: + simple: "true" + page: + simple: "2" + pageSize: + simple: "1" + scanId: + simple: "16" + sortFields: + simple: end_date,status + sortOrder: + simple: desc + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 480, + "y": 370 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "3": + id: "3" + taskid: 2bfe5482-f813-4f98-8273-e8c18a00b9bb + type: regular + task: + id: 2bfe5482-f813-4f98-8273-e8c18a00b9bb + version: -1 + name: Run list-scan-filters Command + description: Lists the filtering, sorting, and pagination capabilities available + for scan records on endpoints/commands that support them. + script: Tenable.io|||tenable-io-list-scan-filters + type: regular + iscommand: true + brand: Tenable.io + nexttasks: + '#none#': + - "4" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 370 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "4": + id: "4" + taskid: 3a5011bf-2735-4df0-880e-c238aae33516 + type: regular + task: + id: 3a5011bf-2735-4df0-880e-c238aae33516 + version: -1 + name: Run export-scan Command + description: Export and download a scan report. + script: Tenable.io|||tenable-io-export-scan + type: regular + iscommand: true + brand: Tenable.io + nexttasks: + '#none#': + - "5" + scriptarguments: + assetId: + simple: "10" + chapters: + simple: vuln_hosts_summary,remediations,compliance_exec,vuln_by_host,compliance,vuln_by_plugin + filterName: + complex: + root: TenableIO.ScanFilter.name + transformers: + - operator: slice + args: + from: + value: + simple: "1" + to: + value: + simple: "3" + filterQuality: + complex: + root: TenableIO.ScanFilter.operators + transformers: + - operator: slice + args: + from: + value: + simple: "1" + to: + value: + simple: "3" + filterSearchType: + simple: or + filterValue: + complex: + root: TenableIO.ScanFilter.control + accessor: regex + transformers: + - operator: slice + args: + from: + value: + simple: "1" + to: + value: + simple: "3" + format: + simple: HTML + historyId: + simple: ${TenableIO.ScanHistory.id} + historyUuid: + simple: ${TenableIO.ScanHistory.scan_uuid} + scanId: + simple: "16" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 265, + "y": 545 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "5": + id: "5" + taskid: 4679396d-12de-4f96-8d83-75b8cf3d89d0 + type: title + task: + id: 4679396d-12de-4f96-8d83-75b8cf3d89d0 + version: -1 + name: Done + type: title + iscommand: false + brand: "" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 265, + "y": 720 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false +view: |- + { + "linkLabelsPosition": {}, + "paper": { + "dimensions": { + "height": 735, + "width": 810, + "x": 50, + "y": 50 + } + } + } +inputs: [] +outputs: [] +fromversion: 5.0.0 +description: '' diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml index 2e2d52e42838..57eaa9834561 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml @@ -5,19 +5,19 @@ starttaskid: "0" tasks: "0": id: "0" - taskid: f7932817-4391-4aa3-863d-73d83a011ecf + taskid: 3eb1d5e6-bcb1-4a56-8c2d-64454ee75da4 type: start task: - id: f7932817-4391-4aa3-863d-73d83a011ecf + id: 3eb1d5e6-bcb1-4a56-8c2d-64454ee75da4 version: -1 name: "" iscommand: false brand: "" - description: '' nexttasks: - '#none#': - - "4" + "#none#": + - "4" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -30,34 +30,34 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 - continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "1": id: "1" - taskid: 56c528de-0cec-449f-8506-afe329207973 + taskid: fc0ab069-67dc-4656-840e-40e061fa5975 type: regular task: - id: 56c528de-0cec-449f-8506-afe329207973 + id: fc0ab069-67dc-4656-840e-40e061fa5975 version: -1 name: tenable-io-list-scans description: Retrive scans from the Tenable platform. - script: '|||tenable-io-list-scans' + script: "|||tenable-io-list-scans" type: regular iscommand: true brand: "" nexttasks: - '#none#': - - "2" + "#none#": + - "2" scriptarguments: retry-count: simple: "2" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 360 + "y": 370 } } note: false @@ -65,36 +65,38 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 - continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "2": id: "2" - taskid: 426ddd77-8f48-46a1-89e8-aa6d4782246a + taskid: 17b6cd73-2dc3-45e7-85d5-ce88bd5fb133 type: regular task: - id: 426ddd77-8f48-46a1-89e8-aa6d4782246a + id: 17b6cd73-2dc3-45e7-85d5-ce88bd5fb133 version: -1 name: tenable-io-get-scan-status - description: 'Check the status of a specific scan using its ID. The status can hold following possible values : Running, Completed and Empty (Ready to run).' - script: '|||tenable-io-get-scan-status' + description: + "Check the status of a specific scan using its ID. The status can + hold following possible values : Running, Completed and Empty (Ready to run)." + script: "|||tenable-io-get-scan-status" type: regular iscommand: true brand: "" nexttasks: - '#none#': - - "3" + "#none#": + - "3" scriptarguments: retry-count: simple: "2" scanId: simple: "16" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 540 + "y": 545 } } note: false @@ -102,35 +104,35 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 - continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "3": id: "3" - taskid: 14a9feb3-a1f1-4464-8063-c83bb3fe4eef + taskid: 3426c644-a954-47ad-8c11-ae8ad0128a1a type: regular task: - id: 14a9feb3-a1f1-4464-8063-c83bb3fe4eef + id: 3426c644-a954-47ad-8c11-ae8ad0128a1a version: -1 name: tenable-io-get-scan-report description: Retrive scan-report for the given scan. - script: '|||tenable-io-get-scan-report' + script: "|||tenable-io-get-scan-report" type: regular iscommand: true brand: "" nexttasks: - '#none#': - - "6" + "#none#": + - "6" scriptarguments: - retry-count: - simple: "2" detailed: simple: "yes" info: simple: "yes" + retry-count: + simple: "2" scanId: simple: "16" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -143,15 +145,14 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 - continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "4": id: "4" - taskid: 7139c144-e5bd-4840-8266-7f2cf0b0055c + taskid: c29ead08-0608-4606-8d8a-47502664aa06 type: regular task: - id: 7139c144-e5bd-4840-8266-7f2cf0b0055c + id: c29ead08-0608-4606-8d8a-47502664aa06 version: -1 name: DeleteContext description: Delete field from context @@ -160,12 +161,13 @@ tasks: iscommand: false brand: "" nexttasks: - '#none#': - - "1" + "#none#": + - "1" scriptarguments: all: simple: "yes" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -178,22 +180,24 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 - continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "5": id: "5" - taskid: 23bbbd46-8773-4cf5-847f-ebe65b135f1c + taskid: 032d62f2-48ee-4da9-8d44-102483d55381 type: regular task: - id: 23bbbd46-8773-4cf5-847f-ebe65b135f1c + id: 032d62f2-48ee-4da9-8d44-102483d55381 version: -1 name: tenable-io-get-vulnerability-details description: Retrieve details for the given vulnerability. - script: '|||tenable-io-get-vulnerability-details' + script: "|||tenable-io-get-vulnerability-details" type: regular iscommand: true brand: "" + nexttasks: + "#none#": + - "7" scriptarguments: retry-count: simple: "2" @@ -202,6 +206,7 @@ tasks: root: TenableIO accessor: Vulnerabilities.[0].Id separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -214,34 +219,33 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 - nexttasks: - '#none#': - - "7" - continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "6": id: "6" - taskid: d3cd8fc0-900f-480e-8e14-dea887416c2d + taskid: d100973b-5dd5-42bc-81e1-9823410df2bd type: regular task: - id: d3cd8fc0-900f-480e-8e14-dea887416c2d + id: d100973b-5dd5-42bc-81e1-9823410df2bd version: -1 name: tenable-io-get-vulnerabilities-by-asset - description: Get a list of up to 5000 of the vulnerabilities recorded for a given asset. By default, this list is sorted by vulnerability count, descending. - script: '|||tenable-io-get-vulnerabilities-by-asset' + description: + Get a list of up to 5000 of the vulnerabilities recorded for a + given asset. By default, this list is sorted by vulnerability count, descending. + script: "|||tenable-io-get-vulnerabilities-by-asset" type: regular iscommand: true brand: "" nexttasks: - '#none#': - - "5" + "#none#": + - "5" scriptarguments: - retry-count: - simple: "2" hostname: simple: ec2-52-50-45-109.eu-west-1.compute.amazonaws.com + retry-count: + simple: "2" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -254,25 +258,24 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 - continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "7": id: "7" - taskid: 5a69fe6a-1c85-4736-84ad-ef961c1693c4 + taskid: af2fd25a-6070-4fcb-8ac0-17f7600230c2 type: regular task: - id: 5a69fe6a-1c85-4736-84ad-ef961c1693c4 + id: af2fd25a-6070-4fcb-8ac0-17f7600230c2 version: -1 name: Tenable-io-export-assets description: Retrieves details for the specified asset to include custom attributes. - script: '|||tenable-io-export-assets' + script: "|||tenable-io-export-assets" type: regular iscommand: true brand: "" nexttasks: - '#none#': - - "10" + "#none#": + - "10" scriptarguments: chunkSize: simple: "500" @@ -296,10 +299,10 @@ tasks: isautoswitchedtoquietmode: false "8": id: "8" - taskid: 1f3807f7-1ab6-4d00-844c-9f7d6f1a056d + taskid: 15d882e0-c3d6-4ff4-84a5-7b3204797396 type: regular task: - id: 1f3807f7-1ab6-4d00-844c-9f7d6f1a056d + id: 15d882e0-c3d6-4ff4-84a5-7b3204797396 version: -1 name: Tenable-io-export-vulnerability description: Retrieves details for the specified asset to include custom attributes. @@ -308,8 +311,8 @@ tasks: iscommand: true brand: Tenable.io nexttasks: - '#none#': - - "9" + "#none#": + - "9" scriptarguments: numAssets: simple: "500" @@ -331,10 +334,10 @@ tasks: isautoswitchedtoquietmode: false "9": id: "9" - taskid: 17027ae1-c221-4daf-8bb0-9081d50375d8 + taskid: 8dc9bb01-ba06-4464-8cd2-4be0fe360fee type: condition task: - id: 17027ae1-c221-4daf-8bb0-9081d50375d8 + id: 8dc9bb01-ba06-4464-8cd2-4be0fe360fee version: -1 name: Verify vulnerabilities type: condition @@ -342,16 +345,16 @@ tasks: brand: "" nexttasks: "yes": - - "11" + - "12" separatecontext: false conditions: - - label: "yes" - condition: - - - operator: isNotEmpty - left: - value: - simple: TenableIO.Vulnerability.asset - iscontext: true + - label: "yes" + condition: + - - operator: isNotEmpty + left: + value: + simple: TenableIO.Vulnerability.asset + iscontext: true continueonerrortype: "" view: |- { @@ -369,10 +372,10 @@ tasks: isautoswitchedtoquietmode: false "10": id: "10" - taskid: 8115f6fa-a10f-4d84-87f4-ea8f4e059f66 + taskid: afb82e34-3d4c-4307-84dc-f7e2f3f866b7 type: condition task: - id: 8115f6fa-a10f-4d84-87f4-ea8f4e059f66 + id: afb82e34-3d4c-4307-84dc-f7e2f3f866b7 version: -1 name: Verify assets type: condition @@ -380,16 +383,16 @@ tasks: brand: "" nexttasks: "yes": - - "8" + - "8" separatecontext: false conditions: - - label: "yes" - condition: - - - operator: isNotEmpty - left: - value: - simple: TenableIO.Asset.id - iscontext: true + - label: "yes" + condition: + - - operator: isNotEmpty + left: + value: + simple: TenableIO.Asset.id + iscontext: true continueonerrortype: "" view: |- { @@ -407,23 +410,58 @@ tasks: isautoswitchedtoquietmode: false "11": id: "11" - taskid: 10c45026-f73b-47fc-878a-26ed8b5ecab4 + taskid: 681e360e-2a88-441d-8141-87cfb3801e5f type: title task: - id: 10c45026-f73b-47fc-878a-26ed8b5ecab4 + id: 681e360e-2a88-441d-8141-87cfb3801e5f version: -1 name: Done type: title iscommand: false brand: "" - description: '' separatecontext: false continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 1920 + "y": 2120 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "12": + id: "12" + taskid: 6095f68c-243e-490f-8729-16a5f268ce9c + type: playbook + task: + id: 6095f68c-243e-490f-8729-16a5f268ce9c + version: -1 + name: Tenable.io Run And Export Scan Test + playbookName: Tenable.io Run And Export Scan Test + type: playbook + iscommand: false + brand: "" + nexttasks: + "#none#": + - "11" + separatecontext: true + continueonerrortype: "" + loop: + iscommand: false + exitCondition: "" + wait: 1 + max: 100 + view: |- + { + "position": { + "x": 50, + "y": 1945 } } note: false @@ -438,7 +476,7 @@ view: |- "linkLabelsPosition": {}, "paper": { "dimensions": { - "height": 1935, + "height": 2135, "width": 380, "x": 50, "y": 50 @@ -448,4 +486,4 @@ view: |- inputs: [] outputs: [] fromversion: 5.0.0 -description: '' +description: "" diff --git a/Utils/tests/id_set.json b/Utils/tests/id_set.json index afc006775334..163dc1a956c3 100644 --- a/Utils/tests/id_set.json +++ b/Utils/tests/id_set.json @@ -40241,6 +40241,22 @@ "pack": "Tenable_io" } }, + { + "Tenable.io Run And Export Scan Test": { + "name": "Tenable.io Run And Export Scan Test", + "file_path": "Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml", + "fromversion": "5.0.0", + "implementing_playbooks": [ + "Tenable.io Scan" + ], + "command_to_integration": { + "tenable-io-list-scan-filters": "", + "tenable-io-get-scan-history": "", + "tenable-io-export-scan": "" + }, + "pack": "Tenable_io" + } + }, { "Test - Cofense Intelligence": { "name": "Test - Cofense Intelligence", From d3ab9e77e11012f6609c1d5ae76ad0223c414787 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 17 Jul 2023 17:19:36 +0300 Subject: [PATCH 14/81] safe_get_json to get_json --- .../Tenable_io/Integrations/Tenable_io/Tenable_io.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index f6c8e2091366..ed8587bac384 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1117,7 +1117,8 @@ def export_vulnerabilities_command(args: Dict[str, Any]) -> PollResult: return request_uuid_export_vulnerabilities(args) -def safe_get_json(response: requests.models.Response) -> dict: +def get_json(response: requests.models.Response) -> dict: + '''Handles error status codes and returns the json from response''' try: response.raise_for_status() @@ -1154,7 +1155,7 @@ def list_scan_filters_request() -> dict: response = requests.get( f'{BASE_URL}filters/scans/reports', headers=HEADERS, verify=USE_SSL) - return safe_get_json(response) + return get_json(response) def scan_filters_human_readable(filters: list) -> str: @@ -1194,7 +1195,7 @@ def get_scan_history_request(scan_id, params) -> dict: params=params, headers=HEADERS, verify=USE_SSL) - return safe_get_json(response) + return get_json(response) def scan_history_human_readable(history: list) -> str: @@ -1279,7 +1280,7 @@ def initiate_export_scan(scan_id: str, args: dict) -> PollResult: response = requests.post( **export_scan_request_args(**args), headers=HEADERS, verify=USE_SSL) - response_json = safe_get_json(response) + response_json = get_json(response) if not (file_id := response_json.get('file')): raise DemistoException( @@ -1301,7 +1302,7 @@ def check_export_scan_status(scan_id: str, file_id: str) -> str: response = requests.get( f'{BASE_URL}scans/{scan_id}/export/{file_id}/status', headers=HEADERS, verify=USE_SSL) - return safe_get_json(response).get('status', '') + return get_json(response).get('status', '') def download_export_scan(scan_id: str, file_id: str, args: dict) -> dict: From 3d7c6e3410b19e353ea700585cbda65586143f8a Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 17 Jul 2023 18:28:40 +0300 Subject: [PATCH 15/81] added descriptions to yml --- .../Integrations/Tenable_io/Tenable_io.py | 28 ++++----- .../Integrations/Tenable_io/Tenable_io.yml | 60 +++++++++++-------- .../Tenable_io/Tenable_io_test.py | 2 +- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index ed8587bac384..8684128bda9c 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1276,7 +1276,7 @@ def export_scan_request_args( return {'url': url, 'params': params, 'json': body} -def initiate_export_scan(scan_id: str, args: dict) -> PollResult: +def initiate_export_scan(args: dict) -> str: response = requests.post( **export_scan_request_args(**args), headers=HEADERS, verify=USE_SSL) @@ -1286,16 +1286,7 @@ def initiate_export_scan(scan_id: str, args: dict) -> PollResult: raise DemistoException( f'Unexpected response from Tenable IO: {response_json}') - return PollResult( - None, True, - {'fileId': file_id, - 'scanId': scan_id, - 'format': args['format']}, # not necessary but avoids confusion - CommandResults( - outputs_prefix='InfoFile', - outputs_key_field='file', - outputs=response_json, - readable_output=f'Exporting File. ID: {file_id}')) + return file_id def check_export_scan_status(scan_id: str, file_id: str) -> str: @@ -1321,10 +1312,8 @@ def download_export_scan(scan_id: str, file_id: str, args: dict) -> dict: requires_polling_arg=False) def export_scan_command(args: dict[str, Any]) -> PollResult: - scan_id, file_id = args['scanId'], args.get('fileId') - - if not file_id: - return initiate_export_scan(scan_id, args) + file_id = args.get('fileId') or initiate_export_scan(args) + scan_id = args['scanId'] match check_export_scan_status(scan_id, file_id): case 'ready': @@ -1340,7 +1329,14 @@ def export_scan_command(args: dict[str, Any]) -> PollResult: case 'loading': return PollResult( - None, continue_to_poll=True) + None, + continue_to_poll=True, + args_for_next_run={ + 'fileId': file_id, + 'scanId': scan_id, + 'format': args['format'], # not necessary but avoids confusion + 'hide_polling_output': True + }) case default: raise DemistoException( diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index a946b89c97c2..b0f27aa4850d 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1093,29 +1093,26 @@ script: name: tenable-io-list-scan-filters outputs: - contextPath: TenableIO.ScanFilter.name - description: '' + description: The name of the scan filter. type: String - contextPath: TenableIO.ScanFilter.readable_name - description: '' + description: The readable name of the scan filter. type: String - contextPath: TenableIO.ScanFilter.control.type - description: '' + description: The type of control associated with the scan filter. type: String - contextPath: TenableIO.ScanFilter.control.regex - description: '' + description: The regular expression used by the scan filter. type: String - contextPath: TenableIO.ScanFilter.control.readable_regex - description: '' + description: An example expression that filter's regular expression would match. type: String - contextPath: TenableIO.ScanFilter.operators - description: '' + description: The operators available for the scan filter. type: String - contextPath: TenableIO.ScanFilter.group_name - description: '' + description: The group name associated with the scan filter. type: Unknown - - contextPath: TenableIO.ScanFilter.control.list - description: '' - type: String - arguments: - name: scanId description: The ID of the scan of which to get the runs. @@ -1153,31 +1150,31 @@ script: name: tenable-io-get-scan-history outputs: - contextPath: TenableIO.ScanHistory.time_end - description: '' + description: The end time of the scan. type: Number - contextPath: TenableIO.ScanHistory.scan_uuid - description: '' + description: The UUID (Universally Unique Identifier) of the scan. type: String - contextPath: TenableIO.ScanHistory.id - description: '' + description: The ID of the scan history. type: Number - contextPath: TenableIO.ScanHistory.is_archived - description: '' + description: Indicates whether the scan history is archived or not. type: Boolean - contextPath: TenableIO.ScanHistory.time_start - description: '' + description: The start time of the scan. type: Number - contextPath: TenableIO.ScanHistory.visibility - description: '' + description: The visibility of the scan. type: String - contextPath: TenableIO.ScanHistory.targets.custom - description: '' + description: Indicates whether custom targets were used in the scan. type: Boolean - contextPath: TenableIO.ScanHistory.targets.default - description: '' - type: Unknown + description: Indicates whether the default targets were used in the scan. + type: Boolean - contextPath: TenableIO.ScanHistory.status - description: '' + description: The status of the scan. type: String - arguments: - name: scanId @@ -1242,16 +1239,27 @@ script: description: The ID of the asset scanned. - name: fileId hidden: true - - name: hide_polling_output - defaultValue: true - hidden: true polling: true description: Export and download a scan report. name: tenable-io-export-scan outputs: - - contextPath: InfoFile.file - description: '' - type: String + - contextPath: File.Size + description: The size of the file in bytes. + type: number + - contextPath: File.Name + description: The name of the file. + type: string + - contextPath: File.EntryID + description: The War Room entry ID of the file. + type: string + - contextPath: File.Info + description: The Format of the file. + type: string + - contextPath: File.Type + description: The type of the file. + type: string + - contextPath: File.Extension + description: The file extension of the file. runonce: false script: '-' subtype: python3 diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index f95f06d2336f..ad440f3db038 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -466,4 +466,4 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques # response.status_code = 200 # response._content = b'blabla' # with pytest.raises(DemistoException, match='Error processing request.'): -# safe_get_json(response) \ No newline at end of file +# safe_get_json(response) From 67e8db4721ece122bb1ad2be8446d889d332142d Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 18 Jul 2023 09:36:22 +0300 Subject: [PATCH 16/81] added user-agent to older commands --- .../Integrations/Tenable_io/Tenable_io.py | 23 ++++++++++--------- .../Integrations/Tenable_io/Tenable_io.yml | 20 ++++++++-------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 8684128bda9c..5eb859cc30f7 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -138,19 +138,20 @@ 'High', 'Critical'] -BASE_URL = demisto.params()['url'] -ACCESS_KEY = demisto.params().get('credentials_access_key', {}).get('password') or demisto.params()['access-key'] -SECRET_KEY = demisto.params().get('credentials_secret_key', {}).get('password') or demisto.params()['secret-key'] +PARAMS = demisto.params() +BASE_URL = PARAMS['url'] +ACCESS_KEY = PARAMS.get('credentials_access_key', {}).get('password') or PARAMS.get('access-key') +SECRET_KEY = PARAMS.get('credentials_secret_key', {}).get('password') or PARAMS.get('secret-key') USER_AGENT_HEADERS_VALUE = 'Integration/1.0 (PAN; Cortex-XSOAR; Build/2.0)' AUTH_HEADERS = {'X-ApiKeys': f"accessKey={ACCESS_KEY}; secretKey={SECRET_KEY}"} -NEW_HEADERS = AUTH_HEADERS | { +HEADERS = AUTH_HEADERS | { 'accept': "application/json", - 'content-type': "application/json" + 'content-type': "application/json", + 'User-Agent': USER_AGENT_HEADERS_VALUE } -HEADERS = NEW_HEADERS | {'User-Agent': USER_AGENT_HEADERS_VALUE} -USE_SSL = not demisto.params()['unsecure'] +USE_SSL = not PARAMS['unsecure'] -if not demisto.params()['proxy']: +if not PARAMS['proxy']: del os.environ['HTTP_PROXY'] del os.environ['HTTPS_PROXY'] del os.environ['http_proxy'] @@ -685,7 +686,7 @@ def export_request(request_params: dict, assets_or_vulns: str) -> dict: dict: The UUID of the assets export job or raise DemistoException. """ full_url = f'{BASE_URL}{assets_or_vulns}/export' - res = requests.post(full_url, headers=NEW_HEADERS, verify=USE_SSL, json=request_params) + res = requests.post(full_url, headers=HEADERS, verify=USE_SSL, json=request_params) if res.status_code != 200: raise DemistoException(res.text) return res.json() @@ -702,7 +703,7 @@ def export_request_with_export_uuid(export_uuid: str, assets_or_vulns: str) -> d dict: Status of the export job or raise DemistoException. """ full_url = f'{BASE_URL}{assets_or_vulns}/export/{export_uuid}/status' - res = requests.get(full_url, headers=NEW_HEADERS, verify=USE_SSL) + res = requests.get(full_url, headers=HEADERS, verify=USE_SSL) if res.status_code != 200: raise DemistoException(res.text) return res.json() @@ -720,7 +721,7 @@ def get_chunks_request(export_uuid: str, chunk_id: str, assets_or_vulns: str) -> dict: Status of the export job or raise DemistoException. """ full_url = f'{BASE_URL}{assets_or_vulns}/export/{export_uuid}/chunks/{chunk_id}' - res = requests.get(full_url, headers=NEW_HEADERS, verify=USE_SSL) + res = requests.get(full_url, headers=HEADERS, verify=USE_SSL) if res.status_code != 200: raise DemistoException(res.text) return res.json() diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index b0f27aa4850d..f11e8f699f57 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1142,7 +1142,7 @@ script: - name: page description: The page number of scan records to retrieve (used for pagination). The page size is defined by the "pageSize" argument. - name: pageSize - description: The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. + description: The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. - name: limit description: The number of records to retrieve. If "pageSize" is defined, this argument is ignored. defaultValue: '50' @@ -1197,7 +1197,7 @@ script: - PDF - CSV - name: chapters - description: | + description: | A list of chapters to include in the export. Chapters can be any of the following options: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. This argument is required if the file format is PDF or HTML. @@ -1209,7 +1209,7 @@ script: - compliance_exec - remediations - vuln_by_plugin - - compliance + - compliance - name: filterName description: | A list of filters to apply to the exported scan report. @@ -1237,28 +1237,26 @@ script: - or - name: assetId description: The ID of the asset scanned. - - name: fileId - hidden: true polling: true description: Export and download a scan report. name: tenable-io-export-scan outputs: - - contextPath: File.Size + - contextPath: InfoFile.Size description: The size of the file in bytes. type: number - - contextPath: File.Name + - contextPath: InfoFile.Name description: The name of the file. type: string - - contextPath: File.EntryID + - contextPath: InfoFile.EntryID description: The War Room entry ID of the file. type: string - - contextPath: File.Info + - contextPath: InfoFile.Info description: The Format of the file. type: string - - contextPath: File.Type + - contextPath: InfoFile.Type description: The type of the file. type: string - - contextPath: File.Extension + - contextPath: InfoFile.Extension description: The file extension of the file. runonce: false script: '-' From a5caefaeb557b24c22cdc53b8e77917ece07dca6 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 18 Jul 2023 16:18:27 +0300 Subject: [PATCH 17/81] unit-tests init --- .../Integrations/Tenable_io/Tenable_io.py | 13 +- .../Tenable_io/Tenable_io_test.py | 190 ++++++++++++++---- .../test_data/get_scan_history.json | 1 + .../test_data/list_scan_filters.json | 74 +++++++ 4 files changed, 229 insertions(+), 49 deletions(-) create mode 100644 Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json create mode 100644 Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 5eb859cc30f7..e8a6cb854cae 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -136,8 +136,8 @@ 'Low', 'Medium', 'High', - 'Critical'] - + 'Critical' +] PARAMS = demisto.params() BASE_URL = PARAMS['url'] ACCESS_KEY = PARAMS.get('credentials_access_key', {}).get('password') or PARAMS.get('access-key') @@ -1135,15 +1135,14 @@ def get_json(response: requests.models.Response) -> dict: ' Only Nessus and CSV formats are supported for scan results that are older than 60 days.' ' - Your command arguments included filter query parameters for scan results that are older than 60 days.', 404: 'Tenable Vulnerability Management cannot find the specified scan.', - 492: 'Too Many Requests', + 429: 'Too Many Requests', } code = response.status_code raise DemistoException( f'Error processing request. Got response status code: {code}' + ( f' - {code_messages[code]}' if code in code_messages - else f'. Full Response:\n{response.text}') - ) from e + else f'. Full Response:\n{response.text}')) except requests.exceptions.JSONDecodeError as e: demisto.debug(str(e)) raise DemistoException( @@ -1172,7 +1171,7 @@ def scan_filters_human_readable(filters: list) -> str: return tableToMarkdown( 'Tenable IO Scan Filters', list(map(flatten, filters)), - headers=list(context_to_hr.keys()), + headers=list(context_to_hr), headerTransform=context_to_hr.get) @@ -1214,7 +1213,7 @@ def scan_history_human_readable(history: list) -> str: return tableToMarkdown( 'Tenable IO Scan History', list(map(flatten, history)), - headers=list(context_to_hr.keys()), + headers=list(context_to_hr), headerTransform=context_to_hr.get) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index ad440f3db038..ffd33f2def9f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -3,6 +3,7 @@ from freezegun import freeze_time from CommonServerPython import * import json +import requests MOCK_PARAMS = { 'access-key': 'fake_access_key', @@ -45,12 +46,26 @@ ] + +class MockResponse: + + def __init__(self, json_data, status_code=200): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + def load_json(filename): with open(f'test_data/{filename}.json') as f: return json.load(f) -def mock_demisto(mocker, mock_args): +def mock_demisto(mocker, mock_args=None): mocker.patch.object(demisto, 'params', return_value=MOCK_PARAMS) mocker.patch.object(demisto, 'args', return_value=mock_args) mocker.patch.object(demisto, 'results') @@ -426,44 +441,135 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques assert response.raw_response == export_vulnerabilities_response -# def test_safe_get_json(mocker): -# """ -# Given: -# - A response from Tenable IO -# When: -# - Running the "list-scan-filters", "get-scan-history" and "export-scan" commands. -# Then: -# - Verify that tenable-io-export-vulnerabilities command works as expected. -# """ -# response = requests.models.Response() -# response.status_code = 200 -# response._content = b'{"key": "value"}' -# assert safe_get_json(response) == {'key': 'value'} - - -# @pytest.mark.parametrize( -# 'status_code, error_message', -# ( -# (400, 'Got response status code: 400 - Possible reasons:\n'), -# (404, 'Got response status code: 404 - Tenable Vulnerability Management cannot find the specified scan.'), -# (429, 'Got response status code: 429 - Too Many Requests'), -# (500, '. Full Response:\n'), -# ) -# ) -# def test_safe_get_json_with_error_codes(mocker, status_code, error_message): -# #mock demisto debug -# response = requests.models.Response() -# response.status_code = status_code -# with pytest.raises(DemistoException, match=error_message): -# safe_get_json(response) - - -# def test_safe_get_json_decode_error(mocker): -# ''' - -# ''' -# response = requests.models.Response() -# response.status_code = 200 -# response._content = b'blabla' -# with pytest.raises(DemistoException, match='Error processing request.'): -# safe_get_json(response) +def test_get_json(mocker): + ''' + Given: + - An expected and valid response from Tenable IO + + When: + - Running the "list-scan-filters", "get-scan-history" and "export-scan" commands. + + Then: + - Verify that the error code is acceptable and the response json is returned. + ''' + from Tenable_io import get_json + + mock_demisto(mocker) + + response = requests.models.Response() + response.status_code = 200 + response._content = b'{"key": "value"}' + assert get_json(response) == {'key': 'value'} + + +@pytest.mark.parametrize( + 'status_code, error_message', + ( + (400, 'Got response status code: 400 - Possible reasons:\n'), + (404, 'Got response status code: 404 - Tenable Vulnerability Management cannot find the specified scan.'), + (429, 'Got response status code: 429 - Too Many Requests'), + (500, '. Full Response:\n'), + ) +) +def test_get_json_with_error_codes(mocker, status_code, error_message): + ''' + Given: + - A response with an unsuccessful status code from Tenable IO + + When: + - Running the "list-scan-filters", "get-scan-history" and "export-scan" commands. + + Then: + - Verify that the error code is caught and an explanatory error is raised. + ''' + from Tenable_io import get_json + + mock_demisto(mocker) + + response = requests.models.Response() + response.status_code = status_code + with pytest.raises(DemistoException, match=error_message): + get_json(response) + + +def test_get_json_decode_error(mocker): + ''' + Given: + - A non-json response from Tenable IO + + When: + - Running the "list-scan-filters", "get-scan-history" and "export-scan" commands. + + Then: + - Verify that the unsuccessful json decoding is caught and an error is raised with the response. + ''' + from Tenable_io import get_json + + mock_demisto(mocker) + + response = requests.models.Response() + response.status_code = 200 + response._content = b'blabla' + with pytest.raises(DemistoException, match='Error processing request. Unexpected response from Tenable IO:\nblabla'): + get_json(response) + + +def test_list_scan_filters_command(mocker): + ''' + Given: + - A request to list Tenable IO scan filters. + + When: + - Running the "list-scan-filters" command. + + Then: + - Verify that tenable-io-list-scan-filters command works as expected. + ''' + from Tenable_io import list_scan_filters_command + + test_data = load_json('list_scan_filters') + + response = MockResponse(test_data['response_json']) + requests_get = mocker.patch.object(requests, 'get', return_value=response) + mock_demisto(mocker) + + results = list_scan_filters_command() + + assert results.outputs == test_data['outputs'] + assert results.readable_output == test_data['readable_output'] + assert results.outputs_prefix == 'TenableIO.ScanFilter' + assert results.outputs_key_field == 'name' + assert requests_get.called_with(**test_data['called_with']) + + +def test_get_scan_history_command(mocker): + ''' + Given: + - A request to get Tenable IO scan history. + + When: + - Running the "get-scan-history" command. + + Then: + - Verify that tenable-io-get-scan-history command works as expected. + ''' + from Tenable_io import get_scan_history_command + + test_data = load_json('get_scan_history') + + response = MockResponse(test_data['response_json']) + requests_get = mocker.patch.object(requests, 'get', return_value=response) + mock_demisto(mocker) + + results = get_scan_history_command(test_data['args']) + + assert results.outputs == test_data['outputs'] + assert results.readable_output == test_data['readable_output'] + assert results.outputs_prefix == 'TenableIO.ScanHistory' + assert results.outputs_key_field == 'id' + assert requests_get.called_with(**test_data['called_with']) + + +def test_export_scan_command(mocker): + + mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported', return_value=None) \ No newline at end of file diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json new file mode 100644 index 000000000000..b3bfe5e92691 --- /dev/null +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json @@ -0,0 +1,74 @@ +{ + "response_json": { + "filters": [ + { + "name": "host.id", + "readable_name": "Asset ID", + "control": { + "type": "entry", + "regex": "[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*", + "readable_regex": "a94fd560-f8d9-4ed1-9b46-cba00c21bcdb" + }, + "operators": [ + "eq", + "neq", + "match", + "nmatch" + ], + "group_name": null + }, + { + "name": "plugin.attributes.exploit_framework_canvas", + "readable_name": "CANVAS Exploit Framework", + "control": { + "type": "dropdown", + "list": [ + "true", + "false" + ] + }, + "operators": [ + "eq", + "neq" + ], + "group_name": null + } + ] + }, + "outputs": [ + { + "name": "host.id", + "readable_name": "Asset ID", + "control": { + "type": "entry", + "regex": "[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*", + "readable_regex": "a94fd560-f8d9-4ed1-9b46-cba00c21bcdb" + }, + "operators": [ + "eq", + "neq", + "match", + "nmatch" + ], + "group_name": null + }, + { + "name": "plugin.attributes.exploit_framework_canvas", + "readable_name": "CANVAS Exploit Framework", + "control": { + "type": "dropdown", + "list": [ + "true", + "false" + ] + }, + "operators": [ + "eq", + "neq" + ], + "group_name": null + } + ], + "readable_output": +"" +} \ No newline at end of file From 8c55b68d70f2c7a56a256b946e570b93c7972351 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 18 Jul 2023 21:24:49 +0300 Subject: [PATCH 18/81] RN + docs --- .../Integrations/Tenable_io/README.md | 234 ++++++++++++++++++ .../Integrations/Tenable_io/Tenable_io.py | 6 +- .../Integrations/Tenable_io/Tenable_io.yml | 2 +- .../Tenable_io/Tenable_io_test.py | 38 ++- .../test_data/get_scan_history.json | 90 ++++++- .../test_data/list_scan_filters.json | 121 +++++---- Packs/Tenable_io/ReleaseNotes/2_1_10.md | 9 + Packs/Tenable_io/pack_metadata.json | 2 +- 8 files changed, 433 insertions(+), 69 deletions(-) create mode 100644 Packs/Tenable_io/ReleaseNotes/2_1_10.md diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index 578d2b32bcb7..9e6c1c3665b2 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1355,3 +1355,237 @@ When inserting invalid arguments, an error message could be returned. >|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| >| fake_uuid | 1.1.1.1 | 1.1.1.1 | Linux Kernel 3.13 on Ubuntu 14.04 (trusty) | general-purpose | fqdn | info | 00000 | Name | | | TCP | 22 | 2024-11-07T11:11:05.906Z | 2024-11-07T11:11:05.906Z | Description | N/A | >| fake_uuid | 1.3.2.1 | 1.3.2.1 | Nutanix | general-purpose | fqdn | info | 00000 | Name | | | TCP | 0 | 2024-11-07T11:11:05.906Z | 2024-11-07T11:11:05.906Z | Description | N/A | +### tenable-io-list-scan-filters + +*** +Lists the filtering, sorting, and pagination capabilities available for scan records on endpoints/commands that support them. + +#### Base Command + +`tenable-io-list-scan-filters` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableIO.ScanFilter.name | String | The name of the scan filter. | +| TenableIO.ScanFilter.readable_name | String | The readable name of the scan filter. | +| TenableIO.ScanFilter.control.type | String | The type of control associated with the scan filter. | +| TenableIO.ScanFilter.control.regex | String | The regular expression used by the scan filter. | +| TenableIO.ScanFilter.control.readable_regex | String | An example expression that filter's regular expression would match. | +| TenableIO.ScanFilter.operators | String | The operators available for the scan filter. | +| TenableIO.ScanFilter.group_name | Unknown | The group name associated with the scan filter. | + +#### Command example +```!tenable-io-list-scan-filters``` +#### Context Example +```json +{ + "TenableIO": { + "ScanFilter": [ + { + "control": { + "readable_regex": "01234567-abcd-ef01-2345-6789abcdef01", + "regex": "[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*", + "type": "entry" + }, + "group_name": null, + "name": "host.id", + "operators": [ + "eq", + "neq", + "match", + "nmatch" + ], + "readable_name": "Asset ID" + }, + { + "control": { + "maxlength": 18, + "readable_regex": "NUMBER", + "regex": "^[0-9]+(,[0-9]+)*", + "type": "entry" + }, + "group_name": null, + "name": "plugin.attributes.bid", + "operators": [ + "eq", + "neq", + "match", + "nmatch" + ], + "readable_name": "Bugtraq ID" + } + ] + } +} +``` + +#### Human Readable Output + +>### Tenable IO Scan Filters +>|Filter name|Filter Readable name|Filter Control type|Filter regex|Readable regex|Filter operators|Filter group name| +>|---|---|---|---|---|---|---| +>| host.id | Asset ID | entry | [0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})* | 01234567-abcd-ef01-2345-6789abcdef01 | eq,
neq,
match,
nmatch | | +>| plugin.attributes.bid | Bugtraq ID | entry | ^[0-9]+(,[0-9]+)* | NUMBER | eq,
neq,
match,
nmatch | | + +### tenable-io-get-scan-history + +*** +Lists the individual runs of the specified scan. + +#### Base Command + +`tenable-io-get-scan-history` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| scanId | The ID of the scan of which to get the runs. | Required | +| sortFields | The fields by which to sort, in the order defined by "sortOrder". Possible values: start_date, end_date, status. Possible values are: start_date, end_date, status. | Optional | +| sortOrder | The direction in which to sort the fields defined by "sortFields". Default is "asc". Possible values are: asc, desc. Default is asc. | Optional | +| excludeRollover | Whether or not to exclude rollover scans from the scan history. Default is false. Possible values are: true, false. Default is false. | Optional | +| page | The 1-indexed page number of scan records to retrieve (used for pagination). The page size is defined by the "pageSize" argument. | Optional | +| pageSize | The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. | Optional | +| limit | The number of records to retrieve. If "pageSize" is defined, this argument is ignored. Default is 50. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableIO.ScanHistory.time_end | Number | The end time of the scan. | +| TenableIO.ScanHistory.scan_uuid | String | The UUID \(Universally Unique Identifier\) of the scan. | +| TenableIO.ScanHistory.id | Number | The ID of the scan history. | +| TenableIO.ScanHistory.is_archived | Boolean | Indicates whether the scan history is archived or not. | +| TenableIO.ScanHistory.time_start | Number | The start time of the scan. | +| TenableIO.ScanHistory.visibility | String | The visibility of the scan. | +| TenableIO.ScanHistory.targets.custom | Boolean | Indicates whether custom targets were used in the scan. | +| TenableIO.ScanHistory.targets.default | Boolean | Indicates whether the default targets were used in the scan. | +| TenableIO.ScanHistory.status | String | The status of the scan. | + +#### Command example +```!tenable-io-get-scan-history scanId=16 excludeRollover=true sortFields=end_date,status sortOrder=desc page=2 pageSize=4``` +#### Context Example +```json +{ + "TenableIO": { + "ScanHistory": [ + { + "id": 17235445, + "is_archived": true, + "reindexing": null, + "scan_uuid": "69a55b8e-0d52-427a-81e0-7dfe4dc6eda6", + "status": "completed", + "targets": { + "custom": null, + "default": false + }, + "time_end": 1677425182, + "time_start": 1677424566, + "visibility": "public" + }, + { + "id": 17235342, + "is_archived": true, + "reindexing": null, + "scan_uuid": "2c592d52-df56-42e0-9f18-d892bdeb1e18", + "status": "completed", + "targets": { + "custom": null, + "default": false + }, + "time_end": 1677424556, + "time_start": 1677423906, + "visibility": "public" + }, + { + "id": 17235033, + "is_archived": true, + "reindexing": null, + "scan_uuid": "44586b4f-1051-415c-b375-db86f6bd8c13", + "status": "completed", + "targets": { + "custom": null, + "default": false + }, + "time_end": 1677423865, + "time_start": 1677423247, + "visibility": "public" + }, + { + "id": 17234969, + "is_archived": true, + "reindexing": null, + "scan_uuid": "06c12bf7-436f-489d-bb04-aae511ea9f5c", + "status": "completed", + "targets": { + "custom": null, + "default": false + }, + "time_end": 1677423205, + "time_start": 1677422585, + "visibility": "public" + } + ] + } +} +``` + +#### Human Readable Output + +>### Tenable IO Scan History +>|History id|History uuid|Status|Is archived|Targets custom|Targets default|Visibility|Time start|Time end| +>|---|---|---|---|---|---|---|---|---| +>| 17235445 | 69a55b8e-0d52-427a-81e0-7dfe4dc6eda6 | completed | true | | false | public | 1677424566 | 1677425182 | +>| 17235342 | 2c592d52-df56-42e0-9f18-d892bdeb1e18 | completed | true | | false | public | 1677423906 | 1677424556 | +>| 17235033 | 44586b4f-1051-415c-b375-db86f6bd8c13 | completed | true | | false | public | 1677423247 | 1677423865 | +>| 17234969 | 06c12bf7-436f-489d-bb04-aae511ea9f5c | completed | true | | false | public | 1677422585 | 1677423205 | + +### tenable-io-export-scan + +*** +Export and download a scan report. + +#### Base Command + +`tenable-io-export-scan` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| scanId | The identifier for the scan to export. Run the "tenable-io-list-scans" command to get all available scans. | Required | +| historyId | The unique identifier of the historical data to export. Run the "tenable-io-get-scan-history" command to get history IDs. | Optional | +| historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | +| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 60 days.
For scans that are older than 60 days, only the Nessus and CSV formats are supported. Default is CSV.
. Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Required | +| chapters | A list of chapters to include in the export.
Chapters can be any of the following options: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance.
This argument is required if the file format is PDF or HTML.
. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | +| filterName | A list of filters to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | +| filterQuality | A list of operators for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | +| filterValue | A list of values for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | +| filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: and, or. Default is and. | Optional | +| assetId | The ID of the asset scanned. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| InfoFile.Size | number | The size of the file in bytes. | +| InfoFile.Name | string | The name of the file. | +| InfoFile.EntryID | string | The War Room entry ID of the file. | +| InfoFile.Info | string | The Format of the file. | +| InfoFile.Type | string | The type of the file. | +| InfoFile.Extension | unknown | The file extension of the file. | + +#### Command example +```!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=or filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue=".*,(.[0-9]0-9)*" assetId=10``` +#### Human Readable Output + +>Preparing scan report: + +>Returned file: scan_16_SSE-144f3dc6-cb2d-42fc-b6cc-dd20b807735f-html.html [Download](https://example.com) \ No newline at end of file diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index e8a6cb854cae..7fd3798dd792 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1170,7 +1170,7 @@ def scan_filters_human_readable(filters: list) -> str: } return tableToMarkdown( 'Tenable IO Scan Filters', - list(map(flatten, filters)), + [d | d.get('control', {}) for d in filters], headers=list(context_to_hr), headerTransform=context_to_hr.get) @@ -1212,7 +1212,7 @@ def scan_history_human_readable(history: list) -> str: } return tableToMarkdown( 'Tenable IO Scan History', - list(map(flatten, history)), + [d | d.get('targets', {}) for d in history], headers=list(context_to_hr), headerTransform=context_to_hr.get) @@ -1224,7 +1224,7 @@ def scan_history_params(args: dict) -> dict: for field in argToList(args.get('sortFields'))), 'exclude_rollover': args['excludeRollover'], 'limit': args.get('pageSize') or args['limit'], - 'offset': int(args.get('pageSize', 0)) * int(args.get('page', 0)), + 'offset': int(args.get('pageSize', 0)) * (int(args.get('page', 0)) - 1), } diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index f11e8f699f57..ac3dd88951b3 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1140,7 +1140,7 @@ script: - 'true' - 'false' - name: page - description: The page number of scan records to retrieve (used for pagination). The page size is defined by the "pageSize" argument. + description: The 1-indexed page number of scan records to retrieve (used for pagination). The page size is defined by the "pageSize" argument. - name: pageSize description: The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. - name: limit diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index ffd33f2def9f..76c7e85d47b3 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -4,6 +4,7 @@ from CommonServerPython import * import json import requests +# type: ignore=operator MOCK_PARAMS = { 'access-key': 'fake_access_key', @@ -46,12 +47,12 @@ ] - class MockResponse: - def __init__(self, json_data, status_code=200): + def __init__(self, json_data, status_code=200, content=None): self.json_data = json_data self.status_code = status_code + self.content = content def json(self): return self.json_data @@ -539,7 +540,9 @@ def test_list_scan_filters_command(mocker): assert results.readable_output == test_data['readable_output'] assert results.outputs_prefix == 'TenableIO.ScanFilter' assert results.outputs_key_field == 'name' - assert requests_get.called_with(**test_data['called_with']) + requests_get.assert_called_with( + *test_data['called_with']['args'], + **test_data['called_with']['kwargs']) def test_get_scan_history_command(mocker): @@ -567,9 +570,26 @@ def test_get_scan_history_command(mocker): assert results.readable_output == test_data['readable_output'] assert results.outputs_prefix == 'TenableIO.ScanHistory' assert results.outputs_key_field == 'id' - assert requests_get.called_with(**test_data['called_with']) - - -def test_export_scan_command(mocker): - - mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported', return_value=None) \ No newline at end of file + requests_get.assert_called_with( + *test_data['called_with']['args'], + **test_data['called_with']['kwargs']) + + +# @pytest.mark.parametrize( + +# ) +# def test_export_scan_command(mocker, args, ): + +# test_data = load_json('export_scan') + +# mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported', return_value=None) +# get_response = MockResponse( +# test_data['get_response_json'], +# content=test_data['get_response_content']) +# post_response = MockResponse( +# test_data['post_response_json']) +# requests_get = mocker.patch.object(requests, 'get', return_value=get_response) +# requests_post = mocker.patch.object(requests, 'post', return_value=post_response) + # args, kwargs = requests_get.call_args + # with open('result.json', 'w') as f: + # json.dump({'args': args, 'kwargs': kwargs}, f) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json index 9e26dfeeb6e6..0d19b5894aa7 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json @@ -1 +1,89 @@ -{} \ No newline at end of file +{ + "response_json": { + "history": [ + { + "time_end": 1545945607, + "scan_uuid": "fc9dc7c5-8eec-4d39-ad9c-20e833cca69b", + "id": 10535512, + "is_archived": true, + "time_start": 1545945482, + "visibility": "public", + "targets": { + "custom": false, + "default": null + }, + "status": "canceled" + }, + { + "time_end": 1545945457, + "scan_uuid": "9e3a89e5-f3d0-4708-9ec9-403a34e7cd5e", + "id": 10535505, + "is_archived": true, + "time_start": 1545945321, + "visibility": "public", + "targets": { + "custom": false, + "default": null + }, + "status": "completed" + } + ] + }, + "outputs": [ + { + "time_end": 1545945607, + "scan_uuid": "fc9dc7c5-8eec-4d39-ad9c-20e833cca69b", + "id": 10535512, + "is_archived": true, + "time_start": 1545945482, + "visibility": "public", + "targets": { + "custom": false, + "default": null + }, + "status": "canceled" + }, + { + "time_end": 1545945457, + "scan_uuid": "9e3a89e5-f3d0-4708-9ec9-403a34e7cd5e", + "id": 10535505, + "is_archived": true, + "time_start": 1545945321, + "visibility": "public", + "targets": { + "custom": false, + "default": null + }, + "status": "completed" + } + ], + "readable_output": "### Tenable IO Scan History\n|History id|History uuid|Status|Is archived|Targets custom|Targets default|Visibility|Time start|Time end|\n|---|---|---|---|---|---|---|---|---|\n| 10535512 | fc9dc7c5-8eec-4d39-ad9c-20e833cca69b | canceled | true | false | | public | 1545945482 | 1545945607 |\n| 10535505 | 9e3a89e5-f3d0-4708-9ec9-403a34e7cd5e | completed | true | false | | public | 1545945321 | 1545945457 |\n", + "args": { + "scanId": "16", + "excludeRollover": "true", + "sortFields": "end_date,status", + "sortOrder": "desc", + "page": "2", + "pageSize": "30" + }, + "called_with": { + "args": [ + "http://123-fake-api.com/scans/16/history" + ], + "kwargs": { + "params": { + "sort": "end_date:desc,status:desc", + "exclude_rollover": "true", + "limit": "30", + "offset": 30 + }, + "headers": { + "X-ApiKeys": "accessKey=fake_access_key; secretKey=fake_access_key", + "accept": "application/json", + "content-type": "application/json", + "User-Agent": "Integration/1.0 (PAN; Cortex-XSOAR; Build/2.0)" + }, + "verify": false + } + } +} \ No newline at end of file diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json index b3bfe5e92691..5b7c94a37766 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json @@ -1,74 +1,87 @@ { "response_json": { "filters": [ - { + { + "name": "host.id", + "readable_name": "Asset ID", + "control": { + "type": "entry", + "regex": "[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*", + "readable_regex": "a94fd560-f8d9-4ed1-9b46-cba00c21bcdb" + }, + "operators": [ + "eq", + "neq", + "match", + "nmatch" + ], + "group_name": null + }, + { + "name": "plugin.attributes.exploit_framework_canvas", + "readable_name": "CANVAS Exploit Framework", + "control": { + "type": "dropdown", + "list": [ + "true", + "false" + ] + }, + "operators": [ + "eq", + "neq" + ], + "group_name": null + } + ] + }, + "outputs": [ + { "name": "host.id", "readable_name": "Asset ID", "control": { - "type": "entry", - "regex": "[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*", - "readable_regex": "a94fd560-f8d9-4ed1-9b46-cba00c21bcdb" + "type": "entry", + "regex": "[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*", + "readable_regex": "a94fd560-f8d9-4ed1-9b46-cba00c21bcdb" }, "operators": [ - "eq", - "neq", - "match", - "nmatch" + "eq", + "neq", + "match", + "nmatch" ], "group_name": null - }, - { + }, + { "name": "plugin.attributes.exploit_framework_canvas", "readable_name": "CANVAS Exploit Framework", "control": { - "type": "dropdown", - "list": [ - "true", - "false" - ] + "type": "dropdown", + "list": [ + "true", + "false" + ] }, "operators": [ - "eq", - "neq" + "eq", + "neq" ], "group_name": null - } - ] - }, - "outputs": [ - { - "name": "host.id", - "readable_name": "Asset ID", - "control": { - "type": "entry", - "regex": "[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*", - "readable_regex": "a94fd560-f8d9-4ed1-9b46-cba00c21bcdb" - }, - "operators": [ - "eq", - "neq", - "match", - "nmatch" - ], - "group_name": null - }, - { - "name": "plugin.attributes.exploit_framework_canvas", - "readable_name": "CANVAS Exploit Framework", - "control": { - "type": "dropdown", - "list": [ - "true", - "false" - ] - }, - "operators": [ - "eq", - "neq" - ], - "group_name": null } ], - "readable_output": -"" + "readable_output": "### Tenable IO Scan Filters\n|Filter name|Filter Readable name|Filter Control type|Filter regex|Readable regex|Filter operators|Filter group name|\n|---|---|---|---|---|---|---|\n| host.id | Asset ID | entry | [0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})* | a94fd560-f8d9-4ed1-9b46-cba00c21bcdb | eq,
neq,
match,
nmatch | |\n| plugin.attributes.exploit_framework_canvas | CANVAS Exploit Framework | dropdown | | | eq,
neq | |\n", + "called_with": { + "args": [ + "http://123-fake-api.com/filters/scans/reports" + ], + "kwargs": { + "headers": { + "X-ApiKeys": "accessKey=fake_access_key; secretKey=fake_access_key", + "accept": "application/json", + "content-type": "application/json", + "User-Agent": "Integration/1.0 (PAN; Cortex-XSOAR; Build/2.0)" + }, + "verify": false + } + } } \ No newline at end of file diff --git a/Packs/Tenable_io/ReleaseNotes/2_1_10.md b/Packs/Tenable_io/ReleaseNotes/2_1_10.md new file mode 100644 index 000000000000..39c6746c30c2 --- /dev/null +++ b/Packs/Tenable_io/ReleaseNotes/2_1_10.md @@ -0,0 +1,9 @@ + +#### Integrations + +##### Tenable.io + +- Added the following commands: + - **tenable-io-list-scan-filters** + - **tenable-io-get-scan-history** + - **tenable-io-export-scan** diff --git a/Packs/Tenable_io/pack_metadata.json b/Packs/Tenable_io/pack_metadata.json index 21091915b20b..83d54ec6cae8 100644 --- a/Packs/Tenable_io/pack_metadata.json +++ b/Packs/Tenable_io/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Tenable.io", "description": "A comprehensive asset centric solution to accurately track resources while accommodating dynamic assets such as cloud, mobile devices, containers and web applications.", "support": "xsoar", - "currentVersion": "2.1.9", + "currentVersion": "2.1.10", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", From 9399b7d8032b54c896baac877575c655c1aab4d4 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Thu, 20 Jul 2023 16:03:45 +0300 Subject: [PATCH 19/81] unit-tests complete --- .../Tenable_io/Tenable_io_test.py | 133 +++++++++++++++--- .../test_data/initiate_export_scan.json | 44 ++++++ 2 files changed, 159 insertions(+), 18 deletions(-) create mode 100644 Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 76c7e85d47b3..fcbfcb50b60b 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -575,21 +575,118 @@ def test_get_scan_history_command(mocker): **test_data['called_with']['kwargs']) -# @pytest.mark.parametrize( - -# ) -# def test_export_scan_command(mocker, args, ): - -# test_data = load_json('export_scan') - -# mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported', return_value=None) -# get_response = MockResponse( -# test_data['get_response_json'], -# content=test_data['get_response_content']) -# post_response = MockResponse( -# test_data['post_response_json']) -# requests_get = mocker.patch.object(requests, 'get', return_value=get_response) -# requests_post = mocker.patch.object(requests, 'post', return_value=post_response) - # args, kwargs = requests_get.call_args - # with open('result.json', 'w') as f: - # json.dump({'args': args, 'kwargs': kwargs}, f) +def test_initiate_export_scan(mocker): + ''' + Given: + - A request to export a scan report. + + When: + - Running the "export-scan" command. + + Then: + - Initiate an export scan request. + ''' + + from Tenable_io import initiate_export_scan + + test_data = load_json('initiate_export_scan') + mock_demisto(mocker) + requests_post = mocker.patch.object( + requests, 'post', return_value=MockResponse(test_data['response_json'])) + file = initiate_export_scan(test_data['args']) + + assert file == test_data['expected_file'] + requests_post.assert_called_with(**test_data['call_args']) + + +def test_check_export_scan_status(mocker): + ''' + Given: + - A request to export a scan report. + + When: + - Running the "export-scan" command. + + Then: + - Initiate an export scan request. + ''' + from Tenable_io import check_export_scan_status, HEADERS + + mock_demisto(mocker) + requests_get = mocker.patch.object( + requests, 'get', return_value=MockResponse({'status': 'ready'})) + + file = check_export_scan_status('scan_id', 'file_id') + + assert file == 'ready' + requests_get.assert_called_with( + 'http://123-fake-api.com/scans/scan_id/export/file_id/status', + headers=HEADERS, verify=False) + + +def test_download_export_scan(mocker): + ''' + Given: + - A request to export a scan report. + + When: + - Running the "export-scan" command. + + Then: + - Initiate an export scan request. + ''' + from Tenable_io import download_export_scan, HEADERS + + mock_demisto(mocker) + requests_get = mocker.patch.object( + requests, 'get', return_value=MockResponse({}, content='content')) + file_result = mocker.patch('CommonServerPython.fileResult') + + download_export_scan('scan_id', 'file_id', {'format': 'HTML'}) + + requests_get.assert_called_with( + 'http://123-fake-api.com/scans/scan_id/export/file_id/download', + headers=HEADERS, verify=False) + file_result.assert_called_with( + 'scan_scan_id_file_id.html', + 'content', EntryType.ENTRY_INFO_FILE) + + +@pytest.mark.parametrize( + 'args, get_response_json, post_response_json, message', + [ + ({'scanId': '', 'format': 'HTML'}, {}, {}, 'The "chapters" field must be provided for PDF or HTML formats.'), + ({'scanId': '', 'format': ''}, {'status': 'ready'}, {}, 'Unexpected response from Tenable IO: {}'), + ({'scanId': '', 'format': ''}, {'status': 'error'}, {'file': 'file_id'}, + 'Tenable IO encountered an error while exporting the scan report file.'), + ({'scanId': '', 'format': ''}, {'status': 'random'}, {'file': 'file_id'}, + 'Got unexpected status while exporting the scan report file: \'random\'') + ] +) +def test_export_scan_command_errors(mocker, args, get_response_json, post_response_json, message): + ''' + Given: + - A request to export a scan report. + + When: + - Running the "export-scan" command in any of the following cases: + - Case A: The "format" arg is HTML or PDF but "chapters" is not defined. + - Case B: The "scans/{scanId}/export" endpoint returns a json without a "file" key. + - Case C: An export scan request has been made and the report status is "error". + - Case D: An export scan request has been made and the report status is unrecognized. + + Then: + - Return an error. + ''' + + from Tenable_io import export_scan_command + + mock_demisto(mocker) + mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported') + get_response = MockResponse(get_response_json) + post_response = MockResponse(post_response_json) + mocker.patch.object(requests, 'get', return_value=get_response) + mocker.patch.object(requests, 'post', return_value=post_response) + + with pytest.raises(DemistoException, match=message): + export_scan_command(args) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json new file mode 100644 index 000000000000..909c2502aecd --- /dev/null +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json @@ -0,0 +1,44 @@ +{ + "args": { + "scanId": "16", + "format": "HTML", + "chapters": "compliance_exec,remediations,vuln_by_plugin", + "historyId": "19540157", + "historyUuid": "f7eaad37-23bd-4aac-a979-baab0e9a465b", + "filterSearchType": "or", + "filterName": "host.id,plugin.attributes.bid", + "filterQuality": "eq,neq", + "filterValue": ".*,(.[0-9]0-9)*", + "assetId": "10" + }, + "response_json": { + "file": "123456789" + }, + "expected_file": "123456789", + "call_args": { + "url": "http://123-fake-api.com/scans/16/export", + "params": { + "history_id": "19540157", + "history_uuid": "f7eaad37-23bd-4aac-a979-baab0e9a465b" + }, + "json": { + "format": "html", + "chapters": "compliance_exec;remediations;vuln_by_plugin", + "filter.search_type": "or", + "asset_id": "10", + "filter.0.filter": "host.id", + "filter.0.quality": "eq", + "filter.0.value": ".*", + "filter.1.filter": "plugin.attributes.bid", + "filter.1.quality": "neq", + "filter.1.value": "(.[0-9]0-9)*" + }, + "headers": { + "X-ApiKeys": "accessKey=fake_access_key; secretKey=fake_access_key", + "accept": "application/json", + "content-type": "application/json", + "User-Agent": "Integration/1.0 (PAN; Cortex-XSOAR; Build/2.0)" + }, + "verify": false + } +} \ No newline at end of file From 6f785f06f949cbaed930f065118dd80c51205fc7 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sat, 22 Jul 2023 21:21:37 +0300 Subject: [PATCH 20/81] fixed tests --- .../Tenable_io/Tenable_io_test.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index fcbfcb50b60b..1ab84962681e 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -69,8 +69,20 @@ def load_json(filename): def mock_demisto(mocker, mock_args=None): mocker.patch.object(demisto, 'params', return_value=MOCK_PARAMS) mocker.patch.object(demisto, 'args', return_value=mock_args) + mocker.patch.object(demisto, 'uniqueFile', return_value='file') + mocker.patch.object(demisto, 'investigation', return_value={'id': 'id'}) mocker.patch.object(demisto, 'results') mocker.patch.object(demisto, 'debug') + # if file_type is None: + # file_type = entryTypes['file'] + # temp = demisto.uniqueFile() + # # pylint: disable=undefined-variable + # if (IS_PY3 and isinstance(data, str)) or (not IS_PY3 and isinstance(data, unicode)): # type: ignore # noqa: F821 + # data = data.encode('utf-8') + # # pylint: enable=undefined-variable + # with open(demisto.investigation()['id'] + '_' + temp, 'wb') as f: + # f.write(data) + # return {'Contents': '', 'ContentsFormat': formats['text'], 'Type': file_type, 'File': filename, 'FileID': temp} def test_get_scan_status(mocker, requests_mock): @@ -640,16 +652,19 @@ def test_download_export_scan(mocker): mock_demisto(mocker) requests_get = mocker.patch.object( requests, 'get', return_value=MockResponse({}, content='content')) - file_result = mocker.patch('CommonServerPython.fileResult') - download_export_scan('scan_id', 'file_id', {'format': 'HTML'}) + result = download_export_scan('scan_id', 'file_id', {'format': 'HTML'}) + assert result == { + 'Contents': '', + 'ContentsFormat': 'text', + 'Type': 9, + 'File': 'scan_scan_id_file_id.html', + 'FileID': 'file', + } requests_get.assert_called_with( 'http://123-fake-api.com/scans/scan_id/export/file_id/download', headers=HEADERS, verify=False) - file_result.assert_called_with( - 'scan_scan_id_file_id.html', - 'content', EntryType.ENTRY_INFO_FILE) @pytest.mark.parametrize( From 3fe5b461e16691f76cd347331d562ecf7742c74b Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sat, 22 Jul 2023 22:21:28 +0300 Subject: [PATCH 21/81] updated docker --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 2 +- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 2 +- Packs/Tenable_io/ReleaseNotes/2_1_10.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 7fd3798dd792..1227fe72ced6 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1142,7 +1142,7 @@ def get_json(response: requests.models.Response) -> dict: f'Error processing request. Got response status code: {code}' + ( f' - {code_messages[code]}' if code in code_messages - else f'. Full Response:\n{response.text}')) + else f'. Full Response:\n{response.text}')) from e except requests.exceptions.JSONDecodeError as e: demisto.debug(str(e)) raise DemistoException( diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index ac3dd88951b3..cb4712ffbb04 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1262,7 +1262,7 @@ script: script: '-' subtype: python3 type: python - dockerimage: demisto/python3:3.10.12.63474 + dockerimage: demisto/python3:3.10.12.65389 tests: - Tenable.io test fromversion: 5.0.0 diff --git a/Packs/Tenable_io/ReleaseNotes/2_1_10.md b/Packs/Tenable_io/ReleaseNotes/2_1_10.md index 39c6746c30c2..ee1c4702a4bd 100644 --- a/Packs/Tenable_io/ReleaseNotes/2_1_10.md +++ b/Packs/Tenable_io/ReleaseNotes/2_1_10.md @@ -7,3 +7,4 @@ - **tenable-io-list-scan-filters** - **tenable-io-get-scan-history** - **tenable-io-export-scan** +- Updated the Docker image to: *demisto/python3:3.10.12.65389*. \ No newline at end of file From f00756726695ad28bb5f2954164a05f0652a6e33 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 23 Jul 2023 12:43:15 +0300 Subject: [PATCH 22/81] CR changes part 1 --- .../Integrations/Tenable_io/README.md | 18 ++--- .../Integrations/Tenable_io/Tenable_io.py | 72 ++++++++++--------- .../Integrations/Tenable_io/Tenable_io.yml | 27 ++++--- .../Tenable_io/Tenable_io_test.py | 10 --- Packs/Tenable_io/ReleaseNotes/2_1_10.md | 6 +- 5 files changed, 61 insertions(+), 72 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index 9e6c1c3665b2..9219020455b2 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1377,7 +1377,7 @@ Lists the filtering, sorting, and pagination capabilities available for scan rec | TenableIO.ScanFilter.readable_name | String | The readable name of the scan filter. | | TenableIO.ScanFilter.control.type | String | The type of control associated with the scan filter. | | TenableIO.ScanFilter.control.regex | String | The regular expression used by the scan filter. | -| TenableIO.ScanFilter.control.readable_regex | String | An example expression that filter's regular expression would match. | +| TenableIO.ScanFilter.control.readable_regex | String | An example expression that the filter's regular expression would match. | | TenableIO.ScanFilter.operators | String | The operators available for the scan filter. | | TenableIO.ScanFilter.group_name | Unknown | The group name associated with the scan filter. | @@ -1448,12 +1448,12 @@ Lists the individual runs of the specified scan. | **Argument Name** | **Description** | **Required** | | --- | --- | --- | | scanId | The ID of the scan of which to get the runs. | Required | -| sortFields | The fields by which to sort, in the order defined by "sortOrder". Possible values: start_date, end_date, status. Possible values are: start_date, end_date, status. | Optional | -| sortOrder | The direction in which to sort the fields defined by "sortFields". Default is "asc". Possible values are: asc, desc. Default is asc. | Optional | -| excludeRollover | Whether or not to exclude rollover scans from the scan history. Default is false. Possible values are: true, false. Default is false. | Optional | -| page | The 1-indexed page number of scan records to retrieve (used for pagination). The page size is defined by the "pageSize" argument. | Optional | +| sortFields | The fields by which to sort, in the order defined by "sortOrder". Possible values are: start_date, end_date, status. | Optional | +| sortOrder | The direction in which to sort the fields defined by "sortFields". Possible values are: asc, desc. Default is asc. | Optional | +| excludeRollover | Whether to exclude rollover scans from the scan history. Default is false. Possible values are: true, false. Default is false. | Optional | +| page | The page number of scan records to retrieve (used for pagination) starting from 1. The page size is defined by the "pageSize" argument. | Optional | | pageSize | The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. | Optional | -| limit | The number of records to retrieve. If "pageSize" is defined, this argument is ignored. Default is 50. | Optional | +| limit | The maximum number of records to retrieve. If "pageSize" is defined, this argument is ignored. Default is 50. | Optional | #### Context Output @@ -1563,8 +1563,8 @@ Export and download a scan report. | scanId | The identifier for the scan to export. Run the "tenable-io-list-scans" command to get all available scans. | Required | | historyId | The unique identifier of the historical data to export. Run the "tenable-io-get-scan-history" command to get history IDs. | Optional | | historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | -| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 60 days.
For scans that are older than 60 days, only the Nessus and CSV formats are supported. Default is CSV.
. Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Required | -| chapters | A list of chapters to include in the export.
Chapters can be any of the following options: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance.
This argument is required if the file format is PDF or HTML.
. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | +| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 60 days.
For scans that are older than 60 days, only the Nessus and CSV formats are supported.
Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Required | +| chapters | A list of chapters to include in the export.
This argument is required if the file format is PDF or HTML.
. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | | filterName | A list of filters to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | | filterQuality | A list of operators for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | | filterValue | A list of values for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | @@ -1578,7 +1578,7 @@ Export and download a scan report. | InfoFile.Size | number | The size of the file in bytes. | | InfoFile.Name | string | The name of the file. | | InfoFile.EntryID | string | The War Room entry ID of the file. | -| InfoFile.Info | string | The Format of the file. | +| InfoFile.Info | string | The format and encoding of the file. | | InfoFile.Type | string | The type of the file. | | InfoFile.Extension | unknown | The file extension of the file. | diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 1227fe72ced6..eb6f75962cda 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1172,7 +1172,8 @@ def scan_filters_human_readable(filters: list) -> str: 'Tenable IO Scan Filters', [d | d.get('control', {}) for d in filters], headers=list(context_to_hr), - headerTransform=context_to_hr.get) + headerTransform=context_to_hr.get, + removeNull=True) def list_scan_filters_command() -> CommandResults: @@ -1214,7 +1215,8 @@ def scan_history_human_readable(history: list) -> str: 'Tenable IO Scan History', [d | d.get('targets', {}) for d in history], headers=list(context_to_hr), - headerTransform=context_to_hr.get) + headerTransform=context_to_hr.get, + removeNull=True) def scan_history_params(args: dict) -> dict: @@ -1261,7 +1263,7 @@ def export_scan_request_args( body = { 'format': args['format'].lower(), 'chapters': chapters, - 'filter.search_type': filterSearchType, + 'filter.search_type': (filterSearchType or '').lower(), 'asset_id': assetId, } for i, (name, quality, value)\ @@ -1335,7 +1337,6 @@ def export_scan_command(args: dict[str, Any]) -> PollResult: 'fileId': file_id, 'scanId': scan_id, 'format': args['format'], # not necessary but avoids confusion - 'hide_polling_output': True }) case default: @@ -1349,37 +1350,38 @@ def main(): # pragma: no cover raise DemistoException('Access Key and Secret Key must be provided.') args = demisto.args() - match demisto.command(): - case 'test-module': - demisto.results(test_module()) - case 'tenable-io-list-scans': - demisto.results(get_scans_command()) - case 'tenable-io-launch-scan': - demisto.results(launch_scan_command()) - case 'tenable-io-get-scan-report': - demisto.results(get_report_command()) - case 'tenable-io-get-vulnerability-details': - demisto.results(get_vulnerability_details_command()) - case 'tenable-io-get-vulnerabilities-by-asset': - demisto.results(get_vulnerabilities_by_asset_command()) - case 'tenable-io-get-scan-status': - demisto.results(get_scan_status_command()) - case 'tenable-io-pause-scan': - demisto.results(pause_scan_command()) - case 'tenable-io-resume-scan': - demisto.results(resume_scan_command()) - case 'tenable-io-get-asset-details': - return_results(get_asset_details_command()) - case 'tenable-io-export-assets': - return_results(export_assets_command(args)) - case 'tenable-io-export-vulnerabilities': - return_results(export_vulnerabilities_command(args)) - case 'tenable-io-list-scan-filters': - return_results(list_scan_filters_command()) - case 'tenable-io-get-scan-history': - return_results(get_scan_history_command(args)) - case 'tenable-io-export-scan': - return_results(export_scan_command(args)) + command = demisto.command() + + if command == 'test-module': + demisto.results(test_module()) + elif command == 'tenable-io-list-scans': + demisto.results(get_scans_command()) + elif command == 'tenable-io-launch-scan': + demisto.results(launch_scan_command()) + elif command == 'tenable-io-get-scan-report': + demisto.results(get_report_command()) + elif command == 'tenable-io-get-vulnerability-details': + demisto.results(get_vulnerability_details_command()) + elif command == 'tenable-io-get-vulnerabilities-by-asset': + demisto.results(get_vulnerabilities_by_asset_command()) + elif command == 'tenable-io-get-scan-status': + demisto.results(get_scan_status_command()) + elif command == 'tenable-io-pause-scan': + demisto.results(pause_scan_command()) + elif command == 'tenable-io-resume-scan': + demisto.results(resume_scan_command()) + elif command == 'tenable-io-get-asset-details': + return_results(get_asset_details_command()) + elif command == 'tenable-io-export-assets': + return_results(export_assets_command(args)) + elif command == 'tenable-io-export-vulnerabilities': + return_results(export_vulnerabilities_command(args)) + elif command == 'tenable-io-list-scan-filters': + return_results(list_scan_filters_command()) + elif command == 'tenable-io-get-scan-history': + return_results(get_scan_history_command(args)) + elif command == 'tenable-io-export-scan': + return_results(export_scan_command(args)) if __name__ in ['__main__', 'builtin', 'builtins']: diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index cb4712ffbb04..cecb924ef858 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1105,7 +1105,7 @@ script: description: The regular expression used by the scan filter. type: String - contextPath: TenableIO.ScanFilter.control.readable_regex - description: An example expression that filter's regular expression would match. + description: An example expression that the filter's regular expression would match. type: String - contextPath: TenableIO.ScanFilter.operators description: The operators available for the scan filter. @@ -1118,7 +1118,7 @@ script: description: The ID of the scan of which to get the runs. required: true - name: sortFields - description: 'The fields by which to sort, in the order defined by "sortOrder". Possible values: start_date, end_date, status.' + description: 'The fields by which to sort, in the order defined by "sortOrder".' isArray: true auto: PREDEFINED predefined: @@ -1126,25 +1126,25 @@ script: - end_date - status - name: sortOrder - description: The direction in which to sort the fields defined by "sortFields". Default is "asc". + description: The direction in which to sort the fields defined by "sortFields". defaultValue: asc auto: PREDEFINED predefined: - asc - desc - name: excludeRollover - description: Whether or not to exclude rollover scans from the scan history. Default is false. + description: Whether to exclude rollover scans from the scan history. defaultValue: 'false' auto: PREDEFINED predefined: - 'true' - 'false' - name: page - description: The 1-indexed page number of scan records to retrieve (used for pagination). The page size is defined by the "pageSize" argument. + description: The page number of scan records to retrieve (used for pagination) starting from 1. The page size is defined by the "pageSize" argument. - name: pageSize description: The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. - name: limit - description: The number of records to retrieve. If "pageSize" is defined, this argument is ignored. + description: The maximum number of records to retrieve. If "pageSize" is defined, this argument is ignored. defaultValue: '50' description: Lists the individual runs of the specified scan. name: tenable-io-get-scan-history @@ -1187,7 +1187,7 @@ script: - name: format description: | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 60 days. - For scans that are older than 60 days, only the Nessus and CSV formats are supported. Default is CSV. + For scans that are older than 60 days, only the Nessus and CSV formats are supported. required: true defaultValue: CSV auto: PREDEFINED @@ -1197,10 +1197,7 @@ script: - PDF - CSV - name: chapters - description: | - A list of chapters to include in the export. - Chapters can be any of the following options: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. - This argument is required if the file format is PDF or HTML. + description: A list of chapters to include in the export. This argument is required if the file format is PDF or HTML. isArray: true auto: PREDEFINED predefined: @@ -1230,11 +1227,11 @@ script: isArray: true - name: filterSearchType description: For multiple filters, specifies whether to use the AND or the OR logical operator. - defaultValue: and + defaultValue: AND auto: PREDEFINED predefined: - - and - - or + - AND + - OR - name: assetId description: The ID of the asset scanned. polling: true @@ -1251,7 +1248,7 @@ script: description: The War Room entry ID of the file. type: string - contextPath: InfoFile.Info - description: The Format of the file. + description: The format and encoding of the file. type: string - contextPath: InfoFile.Type description: The type of the file. diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 1ab84962681e..453031c4ad66 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -73,16 +73,6 @@ def mock_demisto(mocker, mock_args=None): mocker.patch.object(demisto, 'investigation', return_value={'id': 'id'}) mocker.patch.object(demisto, 'results') mocker.patch.object(demisto, 'debug') - # if file_type is None: - # file_type = entryTypes['file'] - # temp = demisto.uniqueFile() - # # pylint: disable=undefined-variable - # if (IS_PY3 and isinstance(data, str)) or (not IS_PY3 and isinstance(data, unicode)): # type: ignore # noqa: F821 - # data = data.encode('utf-8') - # # pylint: enable=undefined-variable - # with open(demisto.investigation()['id'] + '_' + temp, 'wb') as f: - # f.write(data) - # return {'Contents': '', 'ContentsFormat': formats['text'], 'Type': file_type, 'File': filename, 'FileID': temp} def test_get_scan_status(mocker, requests_mock): diff --git a/Packs/Tenable_io/ReleaseNotes/2_1_10.md b/Packs/Tenable_io/ReleaseNotes/2_1_10.md index ee1c4702a4bd..469e58ab98a2 100644 --- a/Packs/Tenable_io/ReleaseNotes/2_1_10.md +++ b/Packs/Tenable_io/ReleaseNotes/2_1_10.md @@ -4,7 +4,7 @@ ##### Tenable.io - Added the following commands: - - **tenable-io-list-scan-filters** - - **tenable-io-get-scan-history** - - **tenable-io-export-scan** + - ***tenable-io-list-scan-filters*** + - ***tenable-io-get-scan-history*** + - ***tenable-io-export-scan*** - Updated the Docker image to: *demisto/python3:3.10.12.65389*. \ No newline at end of file From 186401b4aa890201bec42db93263d6a49822b06e Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 23 Jul 2023 15:30:17 +0300 Subject: [PATCH 23/81] CR changes part 2 --- .../Integrations/Tenable_io/Tenable_io.py | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index eb6f75962cda..de1a673fe62a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -150,13 +150,19 @@ 'User-Agent': USER_AGENT_HEADERS_VALUE } USE_SSL = not PARAMS['unsecure'] +USE_PROXY = PARAMS['proxy'] -if not PARAMS['proxy']: +if not USE_PROXY: del os.environ['HTTP_PROXY'] del os.environ['HTTPS_PROXY'] del os.environ['http_proxy'] del os.environ['https_proxy'] +class TenableOIClient(BaseClient): + + def __init__(self, ok_codes=(), auth=None, timeout=None): + super().__init__(BASE_URL, USE_SSL, USE_PROXY, set(ok_codes) | {200, 201, 204}, HEADERS, auth, timeout=timeout or BaseClient.REQUESTS_TIMEOUT) + def flatten(d): r = {} # type: ignore @@ -1245,42 +1251,40 @@ def get_scan_history_command(args: dict[str, Any]) -> CommandResults: raw_response=response_dict) -def export_scan_request_args( - scanId: str, historyId=None, historyUuid=None, chapters=None, - filterSearchType=None, assetId=None, filterName=None, - filterQuality=None, filterValue=None, **args) -> dict: +def export_scan_body(args: dict) -> dict: - if chapters: + if chapters := args.get('chapters'): chapters = ';'.join(argToList(chapters)) elif args['format'] in ('PDF', 'HTML'): raise DemistoException('The "chapters" field must be provided for PDF or HTML formats.') - url = f'{BASE_URL}scans/{scanId}/export' - params = { - 'history_id': historyId, - 'history_uuid': historyUuid, - } body = { 'format': args['format'].lower(), 'chapters': chapters, - 'filter.search_type': (filterSearchType or '').lower(), - 'asset_id': assetId, + 'filter.search_type': args.get('filterSearchType', '').lower(), + 'asset_id': args.get('assetId'), } - for i, (name, quality, value)\ - in enumerate(zip(*map(argToList, (filterName, filterQuality, filterValue)))): + + filter_lists = (argToList(args.get(f)) for f in ('filterName', 'filterQuality', 'filterValue')) + for i, (name, quality, value) in enumerate(zip(*filter_lists)): body |= { f'filter.{i}.filter': name, f'filter.{i}.quality': quality, f'filter.{i}.value': value, } - remove_nulls_from_dictionary(params) + remove_nulls_from_dictionary(body) - return {'url': url, 'params': params, 'json': body} + return body def initiate_export_scan(args: dict) -> str: response = requests.post( - **export_scan_request_args(**args), + f'{BASE_URL}scans/{args["scanId"]}/export', + params=remove_empty_elements({ + 'history_id': args.get('historyId'), + 'history_uuid': args.get('historyUuid'), + }), + json=export_scan_body(args), headers=HEADERS, verify=USE_SSL) response_json = get_json(response) From db0002eaff6e1d0f94f3de3585b9a5a464357aac Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 23 Jul 2023 17:12:59 +0300 Subject: [PATCH 24/81] temp --- .../Integrations/Tenable_io/Tenable_io.py | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index de1a673fe62a..e2ed7d7a0095 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -158,10 +158,33 @@ del os.environ['http_proxy'] del os.environ['https_proxy'] -class TenableOIClient(BaseClient): - def __init__(self, ok_codes=(), auth=None, timeout=None): - super().__init__(BASE_URL, USE_SSL, USE_PROXY, set(ok_codes) | {200, 201, 204}, HEADERS, auth, timeout=timeout or BaseClient.REQUESTS_TIMEOUT) +class Client(BaseClient): + def list_scan_filters(self): + return self._http_request( + 'GET', 'filters/scans/reports') + + def get_scan_history(self, scan_id, params) -> dict: + return self._http_request( + 'GET', f'scans/{scan_id}/history', + params=remove_empty_elements(params)) + + def initiate_export_scan(self, scan_id: str, params: dict, body: dict) -> dict: + return self._http_request( + 'POST', + f'scans/{scan_id}/export', + params=remove_empty_elements(params), + json=remove_empty_elements(body) + ).get('file') + + def check_export_scan_status(self, scan_id: str, file_id: str) -> str: + return self._http_request( + 'GET', f'scans/{scan_id}/export/{file_id}/status' + ).get('status') + + def download_export_scan(self, scan_id: str, file_id: str, args: dict) -> dict: + return self._http_request( + 'GET', f'scans/{scan_id}/export/{file_id}/download') def flatten(d): @@ -1157,13 +1180,6 @@ def get_json(response: requests.models.Response) -> dict: f'\n{response.text}') from e -def list_scan_filters_request() -> dict: - response = requests.get( - f'{BASE_URL}filters/scans/reports', - headers=HEADERS, verify=USE_SSL) - return get_json(response) - - def scan_filters_human_readable(filters: list) -> str: context_to_hr = { 'name': 'Filter name', @@ -1182,9 +1198,9 @@ def scan_filters_human_readable(filters: list) -> str: removeNull=True) -def list_scan_filters_command() -> CommandResults: +def list_scan_filters_command(client: Client) -> CommandResults: - response_dict = list_scan_filters_request() + response_dict = client.list_scan_filters() filters = response_dict.get('filters', []) return CommandResults( @@ -1195,16 +1211,6 @@ def list_scan_filters_command() -> CommandResults: raw_response=response_dict) -def get_scan_history_request(scan_id, params) -> dict: - remove_nulls_from_dictionary(params) - response = requests.get( - f'{BASE_URL}scans/{scan_id}/history', - params=params, - headers=HEADERS, - verify=USE_SSL) - return get_json(response) - - def scan_history_human_readable(history: list) -> str: context_to_hr = { 'id': 'History id', @@ -1236,9 +1242,9 @@ def scan_history_params(args: dict) -> dict: } -def get_scan_history_command(args: dict[str, Any]) -> CommandResults: +def get_scan_history_command(client: Client, args: dict[str, Any]) -> CommandResults: - response_dict = get_scan_history_request( + response_dict = client.get_scan_history( args['scanId'], scan_history_params(args)) history = response_dict.get('history', []) @@ -1277,7 +1283,7 @@ def export_scan_body(args: dict) -> dict: return body -def initiate_export_scan(args: dict) -> str: +def initiate_export_scan(client: Client, args: dict) -> str: response = requests.post( f'{BASE_URL}scans/{args["scanId"]}/export', params=remove_empty_elements({ @@ -1316,15 +1322,15 @@ def download_export_scan(scan_id: str, file_id: str, args: dict) -> dict: poll_message='Preparing scan report:', interval=15, requires_polling_arg=False) -def export_scan_command(args: dict[str, Any]) -> PollResult: +def export_scan_command(client: Client, args: dict[str, Any]) -> PollResult: - file_id = args.get('fileId') or initiate_export_scan(args) + file_id = args.get('fileId') or initiate_export_scan(client, args) scan_id = args['scanId'] - match check_export_scan_status(scan_id, file_id): + match client.check_export_scan_status(scan_id, file_id): case 'ready': return PollResult( - download_export_scan(scan_id, file_id, args), + client.download_export_scan(scan_id, file_id, args), continue_to_poll=False) case 'error': @@ -1353,6 +1359,13 @@ def main(): # pragma: no cover if not (ACCESS_KEY and SECRET_KEY): raise DemistoException('Access Key and Secret Key must be provided.') + client = Client( + BASE_URL, + verify=USE_SSL, + proxy=USE_PROXY, + ok_codes=(200,), + headers=HEADERS + ) args = demisto.args() command = demisto.command() @@ -1381,11 +1394,11 @@ def main(): # pragma: no cover elif command == 'tenable-io-export-vulnerabilities': return_results(export_vulnerabilities_command(args)) elif command == 'tenable-io-list-scan-filters': - return_results(list_scan_filters_command()) + return_results(list_scan_filters_command(client)) elif command == 'tenable-io-get-scan-history': - return_results(get_scan_history_command(args)) + return_results(get_scan_history_command(client, args)) elif command == 'tenable-io-export-scan': - return_results(export_scan_command(args)) + return_results(export_scan_command(client, args)) if __name__ in ['__main__', 'builtin', 'builtins']: From cbdf11fbba41e31fa390c4d8acec74334bf84174 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 23 Jul 2023 18:44:35 +0300 Subject: [PATCH 25/81] temp --- .../Integrations/Tenable_io/Tenable_io.py | 48 ++++++++----------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index e2ed7d7a0095..7d8db64fda0a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -174,17 +174,20 @@ def initiate_export_scan(self, scan_id: str, params: dict, body: dict) -> dict: 'POST', f'scans/{scan_id}/export', params=remove_empty_elements(params), - json=remove_empty_elements(body) - ).get('file') + json=remove_empty_elements(body)) - def check_export_scan_status(self, scan_id: str, file_id: str) -> str: + def check_export_scan_status(self, scan_id: str, file_id: str) -> dict: return self._http_request( - 'GET', f'scans/{scan_id}/export/{file_id}/status' - ).get('status') + 'GET', f'scans/{scan_id}/export/{file_id}/status') - def download_export_scan(self, scan_id: str, file_id: str, args: dict) -> dict: - return self._http_request( - 'GET', f'scans/{scan_id}/export/{file_id}/download') + def download_export_scan(self, scan_id: str, file_id: str, file_format: str) -> dict: + return fileResult( + f'scan_{scan_id}_{file_id}.{file_format.lower()}', + self._http_request( + 'GET', + f'scans/{scan_id}/export/{file_id}/download', + res_type='content'), + EntryType.ENTRY_INFO_FILE) def flatten(d): @@ -1267,7 +1270,7 @@ def export_scan_body(args: dict) -> dict: body = { 'format': args['format'].lower(), 'chapters': chapters, - 'filter.search_type': args.get('filterSearchType', '').lower(), + 'filter.search_type': args['filterSearchType'].lower(), 'asset_id': args.get('assetId'), } @@ -1301,22 +1304,6 @@ def initiate_export_scan(client: Client, args: dict) -> str: return file_id -def check_export_scan_status(scan_id: str, file_id: str) -> str: - response = requests.get( - f'{BASE_URL}scans/{scan_id}/export/{file_id}/status', - headers=HEADERS, verify=USE_SSL) - return get_json(response).get('status', '') - - -def download_export_scan(scan_id: str, file_id: str, args: dict) -> dict: - response = requests.get( - f'{BASE_URL}scans/{scan_id}/export/{file_id}/download', - headers=HEADERS, verify=USE_SSL) - return fileResult( - f'scan_{scan_id}_{file_id}.{args["format"].lower()}', - response.content, EntryType.ENTRY_INFO_FILE) - - @polling_function( 'tenable-io-export-scan', poll_message='Preparing scan report:', @@ -1324,13 +1311,18 @@ def download_export_scan(scan_id: str, file_id: str, args: dict) -> dict: requires_polling_arg=False) def export_scan_command(client: Client, args: dict[str, Any]) -> PollResult: - file_id = args.get('fileId') or initiate_export_scan(client, args) scan_id = args['scanId'] + file_id = ( + args.get('fileId') + or client.initiate_export_scan( + scan_id, params)) - match client.check_export_scan_status(scan_id, file_id): + status = client.check_export_scan_status(scan_id, file_id).get('status') + match status: case 'ready': return PollResult( - client.download_export_scan(scan_id, file_id, args), + client.download_export_scan( + scan_id, file_id, args['format']), continue_to_poll=False) case 'error': From 2f3693352940b22a3a7a5efd398e7f3569cd81cc Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 24 Jul 2023 12:51:27 +0300 Subject: [PATCH 26/81] client: first test --- .../Integrations/Tenable_io/Tenable_io.py | 183 +++++++++++++----- 1 file changed, 133 insertions(+), 50 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 7d8db64fda0a..1c106fd6b7a1 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -6,6 +6,7 @@ import traceback from datetime import datetime import urllib3 +from typing import Iterable import requests @@ -15,6 +16,72 @@ urllib3.disable_warnings() +def paginate( + limit_arg_name='limit', + page_arg_name='page', + page_size_arg_name='page_size', + api_limit=None, + keys_to_pages=None, + page_start=1): + + def dec(func): + def inner(*arguments, **kwargs): + """ + Args: + args (dict): command arguments (demisto.args()). + *arguments: any additional arguments to the command function. + **kwargs: additional keyword arguments to the command function. + """ + # support for class functions + args = next(arg for arg in arguments if isinstance(arg, dict)) + + keys_to_pages = ( + [keys_to_pages] + if isinstance(keys_to_pages, str) + # could be None or list/tuple + else keys_to_pages or []) + + pages = [] + def get_page(**page_args): + nonlocal pages + pages += dict_safe_get( + func(*arguments, args=args | page_args **kwargs), + keys_to_pages, + return_type=list) + + if (page := args.get(page_arg_name)) and (page_size := args.get(page_size_arg_name)): + + offset = (page - page_start) * page_size + + if offset < 0: + raise ValueError + + get_page( + limit=offset + page_size, + offset=offset) + + else: + limit = args[limit_arg_name] + + if api_limit: + while limit > api_limit: + offset = api_limit * page + get_page( + limit=offset + api_limit, + offset=offset) + limit -= api_limit + + get_page( + limit=limit, + offset=api_limit * page) + + return pages + + return inner + + return dec + + FIELD_NAMES_MAP = { 'ScanType': 'Type', 'ScanStart': 'StartTime', @@ -164,10 +231,13 @@ def list_scan_filters(self): return self._http_request( 'GET', 'filters/scans/reports') - def get_scan_history(self, scan_id, params) -> dict: + @paginate( + page_size_arg_name='pageSize', + keys_to_pages='history') + def get_scan_history(self, scan_id, args) -> dict: return self._http_request( 'GET', f'scans/{scan_id}/history', - params=remove_empty_elements(params)) + params=remove_empty_elements(args)) def initiate_export_scan(self, scan_id: str, params: dict, body: dict) -> dict: return self._http_request( @@ -435,8 +505,8 @@ def send_asset_attributes_request(asset_id: str) -> Dict[str, Any]: return res.json() -def test_module(): - send_scan_request() +def test_module(client: Client): + client.list_scan_filters() return 'ok' @@ -1245,19 +1315,48 @@ def scan_history_params(args: dict) -> dict: } -def get_scan_history_command(client: Client, args: dict[str, Any]) -> CommandResults: +def get_scan_history_command(args: dict[str, Any], client: Client) -> CommandResults: - response_dict = client.get_scan_history( + history = client.get_scan_history( args['scanId'], scan_history_params(args)) - history = response_dict.get('history', []) return CommandResults( outputs_prefix='TenableIO.ScanHistory', outputs_key_field='id', - outputs=history, - readable_output=scan_history_human_readable(history), - raw_response=response_dict) + outputs=history) + + +def build_filters(args: dict) -> dict: + ''' + Makes filter args for the export scan endpoint by sequentially matching + the filterName, filterQuality and filterValue lists in args. + Example: + + filterName: name,description + filterQuality: =,!= + filterValue: test,test2 + + returns: + { + 'filter.0.filter': 'name', + 'filter.0.quality': '=', + 'filter.0.value': 'test', + 'filter.1.filter': 'description', + 'filter.1.quality': '!=', + 'filter.1.value': 'test2', + } + ''' + filters: dict[str, str] = {} + filter_lists = map(argToList, map(args.get, ('filterName', 'filterQuality', 'filterValue'))) + for i, (name, quality, value) in enumerate(zip(*filter_lists)): + filters |= { + f'filter.{i}.filter': name, + f'filter.{i}.quality': quality, + f'filter.{i}.value': value, + } + + return filters def export_scan_body(args: dict) -> dict: @@ -1272,36 +1371,19 @@ def export_scan_body(args: dict) -> dict: 'chapters': chapters, 'filter.search_type': args['filterSearchType'].lower(), 'asset_id': args.get('assetId'), - } - - filter_lists = (argToList(args.get(f)) for f in ('filterName', 'filterQuality', 'filterValue')) - for i, (name, quality, value) in enumerate(zip(*filter_lists)): - body |= { - f'filter.{i}.filter': name, - f'filter.{i}.quality': quality, - f'filter.{i}.value': value, - } - + } | build_filters(args) remove_nulls_from_dictionary(body) return body -def initiate_export_scan(client: Client, args: dict) -> str: - response = requests.post( - f'{BASE_URL}scans/{args["scanId"]}/export', +def initiate_export_scan(args: dict, client: Client) -> str: + return client.initiate_export_scan( + args['scanId'], params=remove_empty_elements({ 'history_id': args.get('historyId'), - 'history_uuid': args.get('historyUuid'), - }), - json=export_scan_body(args), - headers=HEADERS, verify=USE_SSL) - response_json = get_json(response) - - if not (file_id := response_json.get('file')): - raise DemistoException( - f'Unexpected response from Tenable IO: {response_json}') - - return file_id + 'history_uuid': args.get('historyUuid')}), + body=export_scan_body(args) + ).get('file', '') @polling_function( @@ -1309,28 +1391,26 @@ def initiate_export_scan(client: Client, args: dict) -> str: poll_message='Preparing scan report:', interval=15, requires_polling_arg=False) -def export_scan_command(client: Client, args: dict[str, Any]) -> PollResult: +def export_scan_command(args: dict[str, Any], client: Client) -> PollResult: + ''' + Calls three endpoints. The first (called with initiate_export_scan) initiates an export and returns a file ID. + The second (called with client.check_export_scan_status) checks the status of the export and the function polls + until the status is 'ready'. The third endpoint is then called (with client.download_export_scan) which downloads + the file and returns a dict with it's contents (using fileResult). + ''' scan_id = args['scanId'] file_id = ( args.get('fileId') - or client.initiate_export_scan( - scan_id, params)) + or initiate_export_scan(args, client)) - status = client.check_export_scan_status(scan_id, file_id).get('status') - match status: + match client.check_export_scan_status(scan_id, file_id).get('status'): case 'ready': return PollResult( client.download_export_scan( scan_id, file_id, args['format']), continue_to_poll=False) - case 'error': - raise DemistoException( - 'Tenable IO encountered an error while exporting the scan report file.\n' - f'Scan ID: {scan_id}\n' - f'File ID: {file_id}\n') - case 'loading': return PollResult( None, @@ -1341,9 +1421,12 @@ def export_scan_command(client: Client, args: dict[str, Any]) -> PollResult: 'format': args['format'], # not necessary but avoids confusion }) - case default: + case error: + demisto.debug(f'{error=}') raise DemistoException( - f'Got unexpected status while exporting the scan report file: {default!r}') + 'Tenable IO encountered an error while exporting the scan report file.\n' + f'Scan ID: {scan_id}\n' + f'File ID: {file_id}\n') def main(): # pragma: no cover @@ -1362,7 +1445,7 @@ def main(): # pragma: no cover command = demisto.command() if command == 'test-module': - demisto.results(test_module()) + demisto.results(test_module(client)) elif command == 'tenable-io-list-scans': demisto.results(get_scans_command()) elif command == 'tenable-io-launch-scan': @@ -1388,9 +1471,9 @@ def main(): # pragma: no cover elif command == 'tenable-io-list-scan-filters': return_results(list_scan_filters_command(client)) elif command == 'tenable-io-get-scan-history': - return_results(get_scan_history_command(client, args)) + return_results(get_scan_history_command(args, client)) elif command == 'tenable-io-export-scan': - return_results(export_scan_command(client, args)) + return_results(export_scan_command(args, client)) if __name__ in ['__main__', 'builtin', 'builtins']: From 4db4220ecef412cf5ab6d17c61ad2f0731b73c12 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 24 Jul 2023 14:46:46 +0300 Subject: [PATCH 27/81] client: working --- .../Integrations/Tenable_io/Tenable_io.py | 111 +++++++----------- 1 file changed, 43 insertions(+), 68 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 1c106fd6b7a1..c37acbe99d1f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -6,7 +6,6 @@ import traceback from datetime import datetime import urllib3 -from typing import Iterable import requests @@ -25,7 +24,7 @@ def paginate( page_start=1): def dec(func): - def inner(*arguments, **kwargs): + def inner(args, *arguments, **kwargs): """ Args: args (dict): command arguments (demisto.args()). @@ -33,47 +32,49 @@ def inner(*arguments, **kwargs): **kwargs: additional keyword arguments to the command function. """ # support for class functions - args = next(arg for arg in arguments if isinstance(arg, dict)) - keys_to_pages = ( + keys = ( [keys_to_pages] if isinstance(keys_to_pages, str) # could be None or list/tuple else keys_to_pages or []) - pages = [] + def get_page(**page_args): nonlocal pages + demisto.debug(f'{page_args=}') pages += dict_safe_get( - func(*arguments, args=args | page_args **kwargs), - keys_to_pages, - return_type=list) - - if (page := args.get(page_arg_name)) and (page_size := args.get(page_size_arg_name)): + func(args | page_args, *arguments, **kwargs), + keys, default_return_value=[], return_type=list) + page, page_size = map(arg_to_number, map(args.get, (page_arg_name, page_size_arg_name))) + if isinstance(page, int) and isinstance(page_size, int): + demisto.debug(f'{page=}, {page_size=}') offset = (page - page_start) * page_size if offset < 0: raise ValueError get_page( - limit=offset + page_size, + limit=page_size, offset=offset) else: - limit = args[limit_arg_name] + limit = arg_to_number(args[limit_arg_name]) + demisto.debug(f'{limit=}') + offset = 0 if api_limit: while limit > api_limit: - offset = api_limit * page get_page( - limit=offset + api_limit, + limit=api_limit, offset=offset) limit -= api_limit + offset += api_limit get_page( limit=limit, - offset=api_limit * page) + offset=offset) return pages @@ -231,20 +232,19 @@ def list_scan_filters(self): return self._http_request( 'GET', 'filters/scans/reports') - @paginate( - page_size_arg_name='pageSize', - keys_to_pages='history') - def get_scan_history(self, scan_id, args) -> dict: + def get_scan_history(self, scan_id, params) -> dict: + remove_nulls_from_dictionary(params) return self._http_request( 'GET', f'scans/{scan_id}/history', - params=remove_empty_elements(args)) + params=params) def initiate_export_scan(self, scan_id: str, params: dict, body: dict) -> dict: + remove_nulls_from_dictionary(params) + remove_nulls_from_dictionary(body) return self._http_request( 'POST', f'scans/{scan_id}/export', - params=remove_empty_elements(params), - json=remove_empty_elements(body)) + params=params, json_data=body) def check_export_scan_status(self, scan_id: str, file_id: str) -> dict: return self._http_request( @@ -254,12 +254,18 @@ def download_export_scan(self, scan_id: str, file_id: str, file_format: str) -> return fileResult( f'scan_{scan_id}_{file_id}.{file_format.lower()}', self._http_request( - 'GET', - f'scans/{scan_id}/export/{file_id}/download', - res_type='content'), + 'GET', f'scans/{scan_id}/export/{file_id}/download', + resp_type='content'), EntryType.ENTRY_INFO_FILE) +def sub_dict(d: dict, *keys): + return { + key: d.get(key) + for key in keys + } + + def flatten(d): r = {} # type: ignore for v in d.values(): @@ -1220,39 +1226,6 @@ def export_vulnerabilities_command(args: Dict[str, Any]) -> PollResult: return request_uuid_export_vulnerabilities(args) -def get_json(response: requests.models.Response) -> dict: - '''Handles error status codes and returns the json from response''' - - try: - response.raise_for_status() - return response.json() - except HTTPError as e: - demisto.debug(f'Response: {response.text}\nError: {e}') - code_messages = { - 400: - 'Possible reasons:\n' - ' - Your request message is missing a required parameter\n.' - ' - The "format" command argument specified an unsupported' - ' export format for scan results that are older than 60 days.\n' - ' Only Nessus and CSV formats are supported for scan results that are older than 60 days.' - ' - Your command arguments included filter query parameters for scan results that are older than 60 days.', - 404: 'Tenable Vulnerability Management cannot find the specified scan.', - 429: 'Too Many Requests', - } - code = response.status_code - raise DemistoException( - f'Error processing request. Got response status code: {code}' + ( - f' - {code_messages[code]}' - if code in code_messages - else f'. Full Response:\n{response.text}')) from e - except requests.exceptions.JSONDecodeError as e: - demisto.debug(str(e)) - raise DemistoException( - 'Error processing request. ' - 'Unexpected response from Tenable IO:' - f'\n{response.text}') from e - - def scan_filters_human_readable(filters: list) -> str: context_to_hr = { 'name': 'Filter name', @@ -1310,17 +1283,22 @@ def scan_history_params(args: dict) -> dict: f'{field}:{args["sortOrder"]}' for field in argToList(args.get('sortFields'))), 'exclude_rollover': args['excludeRollover'], - 'limit': args.get('pageSize') or args['limit'], - 'offset': int(args.get('pageSize', 0)) * (int(args.get('page', 0)) - 1), - } - + } | sub_dict(args, 'limit', 'offset') -def get_scan_history_command(args: dict[str, Any], client: Client) -> CommandResults: - history = client.get_scan_history( +@paginate( + page_size_arg_name='pageSize', + keys_to_pages='history') +def paginated_scan_history(args: dict[str, Any], client: Client): + client.get_scan_history( args['scanId'], scan_history_params(args)) + +def get_scan_history_command(*args) -> CommandResults: + + history = paginated_scan_history(*args) + return CommandResults( outputs_prefix='TenableIO.ScanHistory', outputs_key_field='id', @@ -1372,16 +1350,13 @@ def export_scan_body(args: dict) -> dict: 'filter.search_type': args['filterSearchType'].lower(), 'asset_id': args.get('assetId'), } | build_filters(args) - remove_nulls_from_dictionary(body) return body def initiate_export_scan(args: dict, client: Client) -> str: return client.initiate_export_scan( args['scanId'], - params=remove_empty_elements({ - 'history_id': args.get('historyId'), - 'history_uuid': args.get('historyUuid')}), + params=sub_dict(args, 'historyId', 'historyUuid'), body=export_scan_body(args) ).get('file', '') From 34a326184a2a96f28c614055eba35b1aa85d131b Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 25 Jul 2023 16:48:12 +0300 Subject: [PATCH 28/81] paginate version 1 --- .../Integrations/Tenable_io/Tenable_io.py | 63 +++-- .../Tenable_io/Tenable_io_test.py | 217 ++++++++++-------- .../test_data/get_scan_history.json | 16 +- .../test_data/list_scan_filters.json | 16 +- 4 files changed, 164 insertions(+), 148 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index c37acbe99d1f..d93ef4bdf4a8 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -38,43 +38,39 @@ def inner(args, *arguments, **kwargs): if isinstance(keys_to_pages, str) # could be None or list/tuple else keys_to_pages or []) - pages = [] def get_page(**page_args): - nonlocal pages demisto.debug(f'{page_args=}') - pages += dict_safe_get( + return dict_safe_get( func(args | page_args, *arguments, **kwargs), - keys, default_return_value=[], return_type=list) + keys, return_type=list) + pages: list[Any] = [] - page, page_size = map(arg_to_number, map(args.get, (page_arg_name, page_size_arg_name))) - if isinstance(page, int) and isinstance(page_size, int): - demisto.debug(f'{page=}, {page_size=}') - offset = (page - page_start) * page_size - - if offset < 0: - raise ValueError + try: + page, page_size = map(int, map(args.get, (page_arg_name, page_size_arg_name))) + limit, offset = page_size, (page - page_start) * page_size - get_page( - limit=page_size, - offset=offset) + except (ValueError, TypeError): # from conversion to int - else: - limit = arg_to_number(args[limit_arg_name]) - demisto.debug(f'{limit=}') - offset = 0 + limit, offset = int(args[limit_arg_name]), 0 if api_limit: - while limit > api_limit: - get_page( - limit=api_limit, - offset=offset) - limit -= api_limit - offset += api_limit - - get_page( - limit=limit, - offset=offset) + pages += sum( + ( + get_page( + limit=api_limit, + offset=(offset := curr_offset)) + for curr_offset + in range(0, limit - api_limit, api_limit) + ), + [] + ) + limit = limit % api_limit or api_limit + offset += api_limit + + pages += get_page( + limit=limit, + offset=offset) return pages @@ -1257,7 +1253,7 @@ def list_scan_filters_command(client: Client) -> CommandResults: raw_response=response_dict) -def scan_history_human_readable(history: list) -> str: +def scan_history_readable(history: list) -> str: context_to_hr = { 'id': 'History id', 'scan_uuid': 'History uuid', @@ -1289,20 +1285,21 @@ def scan_history_params(args: dict) -> dict: @paginate( page_size_arg_name='pageSize', keys_to_pages='history') -def paginated_scan_history(args: dict[str, Any], client: Client): - client.get_scan_history( +def scan_history_request(args: dict[str, Any], client: Client): + return client.get_scan_history( args['scanId'], scan_history_params(args)) def get_scan_history_command(*args) -> CommandResults: - history = paginated_scan_history(*args) + history = scan_history_request(*args) return CommandResults( outputs_prefix='TenableIO.ScanHistory', outputs_key_field='id', - outputs=history) + outputs=history, + readable_output=scan_history_readable(history)) def build_filters(args: dict) -> dict: diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 453031c4ad66..bd798c01060a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -3,7 +3,7 @@ from freezegun import freeze_time from CommonServerPython import * import json -import requests +from Tenable_io import Client, HEADERS # type: ignore=operator MOCK_PARAMS = { @@ -45,7 +45,13 @@ 'VulnerabilityState': 'Resurfaced' } ] - +MOCK_CLIENT = Client( + MOCK_PARAMS['url'], + verify=True, + proxy=True, + ok_codes=(200,), + headers=HEADERS +) class MockResponse: @@ -444,79 +450,6 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques assert response.raw_response == export_vulnerabilities_response -def test_get_json(mocker): - ''' - Given: - - An expected and valid response from Tenable IO - - When: - - Running the "list-scan-filters", "get-scan-history" and "export-scan" commands. - - Then: - - Verify that the error code is acceptable and the response json is returned. - ''' - from Tenable_io import get_json - - mock_demisto(mocker) - - response = requests.models.Response() - response.status_code = 200 - response._content = b'{"key": "value"}' - assert get_json(response) == {'key': 'value'} - - -@pytest.mark.parametrize( - 'status_code, error_message', - ( - (400, 'Got response status code: 400 - Possible reasons:\n'), - (404, 'Got response status code: 404 - Tenable Vulnerability Management cannot find the specified scan.'), - (429, 'Got response status code: 429 - Too Many Requests'), - (500, '. Full Response:\n'), - ) -) -def test_get_json_with_error_codes(mocker, status_code, error_message): - ''' - Given: - - A response with an unsuccessful status code from Tenable IO - - When: - - Running the "list-scan-filters", "get-scan-history" and "export-scan" commands. - - Then: - - Verify that the error code is caught and an explanatory error is raised. - ''' - from Tenable_io import get_json - - mock_demisto(mocker) - - response = requests.models.Response() - response.status_code = status_code - with pytest.raises(DemistoException, match=error_message): - get_json(response) - - -def test_get_json_decode_error(mocker): - ''' - Given: - - A non-json response from Tenable IO - - When: - - Running the "list-scan-filters", "get-scan-history" and "export-scan" commands. - - Then: - - Verify that the unsuccessful json decoding is caught and an error is raised with the response. - ''' - from Tenable_io import get_json - - mock_demisto(mocker) - - response = requests.models.Response() - response.status_code = 200 - response._content = b'blabla' - with pytest.raises(DemistoException, match='Error processing request. Unexpected response from Tenable IO:\nblabla'): - get_json(response) - - def test_list_scan_filters_command(mocker): ''' Given: @@ -532,19 +465,17 @@ def test_list_scan_filters_command(mocker): test_data = load_json('list_scan_filters') - response = MockResponse(test_data['response_json']) - requests_get = mocker.patch.object(requests, 'get', return_value=response) + request = mocker.patch.object(BaseClient, '_http_request', return_value=test_data['response_json']) mock_demisto(mocker) - results = list_scan_filters_command() + results = list_scan_filters_command(MOCK_CLIENT) assert results.outputs == test_data['outputs'] assert results.readable_output == test_data['readable_output'] assert results.outputs_prefix == 'TenableIO.ScanFilter' assert results.outputs_key_field == 'name' - requests_get.assert_called_with( - *test_data['called_with']['args'], - **test_data['called_with']['kwargs']) + + request.assert_called_with(*test_data['called_with']['args']) def test_get_scan_history_command(mocker): @@ -562,21 +493,23 @@ def test_get_scan_history_command(mocker): test_data = load_json('get_scan_history') - response = MockResponse(test_data['response_json']) - requests_get = mocker.patch.object(requests, 'get', return_value=response) + request = mocker.patch.object( + BaseClient, '_http_request', return_value=test_data['response_json']) mock_demisto(mocker) - results = get_scan_history_command(test_data['args']) + results = get_scan_history_command(test_data['args'], MOCK_CLIENT) - assert results.outputs == test_data['outputs'] - assert results.readable_output == test_data['readable_output'] assert results.outputs_prefix == 'TenableIO.ScanHistory' assert results.outputs_key_field == 'id' - requests_get.assert_called_with( - *test_data['called_with']['args'], - **test_data['called_with']['kwargs']) + assert results.outputs == test_data['outputs'] + assert results.readable_output == test_data['readable_output'] + assert request.call_args.args == tuple(test_data['called_with']['args']) + assert request.call_args.kwargs == test_data['called_with']['kwargs'] + + +""" def test_initiate_export_scan(mocker): ''' Given: @@ -594,12 +527,16 @@ def test_initiate_export_scan(mocker): test_data = load_json('initiate_export_scan') mock_demisto(mocker) requests_post = mocker.patch.object( - requests, 'post', return_value=MockResponse(test_data['response_json'])) - file = initiate_export_scan(test_data['args']) + BaseClient, '_http_request', return_value=test_data['response_json']) + file = initiate_export_scan(test_data['args'], MOCK_CLIENT) assert file == test_data['expected_file'] - requests_post.assert_called_with(**test_data['call_args']) + # TEMP + test_data['call_args'] = list(requests_post.call_args.args) + with open('test_data/initiate_export_scan.json', 'w') as f: + json.dump(test_data, f) + # requests_post.assert_called_with(**test_data['call_args']) def test_check_export_scan_status(mocker): ''' @@ -621,6 +558,7 @@ def test_check_export_scan_status(mocker): file = check_export_scan_status('scan_id', 'file_id') assert file == 'ready' + requests_get.assert_called_with( 'http://123-fake-api.com/scans/scan_id/export/file_id/status', headers=HEADERS, verify=False) @@ -695,3 +633,98 @@ def test_export_scan_command_errors(mocker, args, get_response_json, post_respon with pytest.raises(DemistoException, match=message): export_scan_command(args) + + +""" + + +@pytest.mark.parametrize( + 'paginate_args, args, embeder, expected_result, expected_call_count, fail_message', + ( + ( + # when limit is smaller than api_limit, call multiple times: + { + "api_limit": 5, + }, + { + "limit": 23 + }, + "{}", + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22], + 5, + "@paginate did not paginate five times" + ), + ( + # when keys_to_pages is provided, extract the pages: + { + "keys_to_pages": ('a', 'b'), + }, + { + "limit": 10 + }, + "{{'a': {{'b': {} }} }}", + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + 1, + "@paginate did not extract the pages" + ), + ( + # when page_size and page are in args, don't use limit and get pages in respect to page_start: + { + "api_limit": 1, + "page_start": 0, + }, + { + "page": 10, + "page_size": 5, + "limit": 50 + }, + "{}", + [50, 51, 52, 53, 54], + 1, + "@paginate used limit or did not use page_start" + ), + ( + # mixed bag: + { + "limit_arg_name": 'any', + "page_arg_name": 'fake_page', + "page_size_arg_name": 'fake_page_size', + "api_limit": 5, + "keys_to_pages": 'a', + "page_start": 1 + }, + { + "fake_page": 10, + "fake_page_size": 5, + "limit": 50, + "page_size": 23 + }, + "{{'a': {} }}", + [45, 46, 47, 48, 49], + 1, + "@paginate failed mixed bag" + ) + ) +) +def test_paginate(paginate_args, args, embeder, expected_result, expected_call_count, fail_message): + + from Tenable_io import paginate + + call_count = 0 + + @paginate(**paginate_args) + def pagination_func(args): + nonlocal call_count + call_count += 1 + limit = int(args['limit']) + offset = int(args['offset']) + return eval(embeder.format( # noqa: PGH001 + list(range( + offset, + limit + offset + )))) + + result = pagination_func(args) + + assert call_count == expected_call_count + assert result == expected_result, fail_message diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json index 0d19b5894aa7..132361671042 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/get_scan_history.json @@ -57,7 +57,7 @@ "status": "completed" } ], - "readable_output": "### Tenable IO Scan History\n|History id|History uuid|Status|Is archived|Targets custom|Targets default|Visibility|Time start|Time end|\n|---|---|---|---|---|---|---|---|---|\n| 10535512 | fc9dc7c5-8eec-4d39-ad9c-20e833cca69b | canceled | true | false | | public | 1545945482 | 1545945607 |\n| 10535505 | 9e3a89e5-f3d0-4708-9ec9-403a34e7cd5e | completed | true | false | | public | 1545945321 | 1545945457 |\n", + "readable_output": "### Tenable IO Scan History\n|History id|History uuid|Status|Is archived|Targets custom|Visibility|Time start|Time end|\n|---|---|---|---|---|---|---|---|\n| 10535512 | fc9dc7c5-8eec-4d39-ad9c-20e833cca69b | canceled | true | false | public | 1545945482 | 1545945607 |\n| 10535505 | 9e3a89e5-f3d0-4708-9ec9-403a34e7cd5e | completed | true | false | public | 1545945321 | 1545945457 |\n", "args": { "scanId": "16", "excludeRollover": "true", @@ -68,22 +68,16 @@ }, "called_with": { "args": [ - "http://123-fake-api.com/scans/16/history" + "GET", + "scans/16/history" ], "kwargs": { "params": { "sort": "end_date:desc,status:desc", "exclude_rollover": "true", - "limit": "30", + "limit": 30, "offset": 30 - }, - "headers": { - "X-ApiKeys": "accessKey=fake_access_key; secretKey=fake_access_key", - "accept": "application/json", - "content-type": "application/json", - "User-Agent": "Integration/1.0 (PAN; Cortex-XSOAR; Build/2.0)" - }, - "verify": false + } } } } \ No newline at end of file diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json index 5b7c94a37766..0321a72825b4 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json @@ -69,19 +69,11 @@ "group_name": null } ], - "readable_output": "### Tenable IO Scan Filters\n|Filter name|Filter Readable name|Filter Control type|Filter regex|Readable regex|Filter operators|Filter group name|\n|---|---|---|---|---|---|---|\n| host.id | Asset ID | entry | [0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})* | a94fd560-f8d9-4ed1-9b46-cba00c21bcdb | eq,
neq,
match,
nmatch | |\n| plugin.attributes.exploit_framework_canvas | CANVAS Exploit Framework | dropdown | | | eq,
neq | |\n", + "readable_output": "### Tenable IO Scan Filters\n|Filter name|Filter Readable name|Filter Control type|Filter regex|Readable regex|Filter operators|\n|---|---|---|---|---|---|\n| host.id | Asset ID | entry | [0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})* | a94fd560-f8d9-4ed1-9b46-cba00c21bcdb | eq,
neq,
match,
nmatch |\n| plugin.attributes.exploit_framework_canvas | CANVAS Exploit Framework | dropdown | | | eq,
neq |\n", "called_with": { "args": [ - "http://123-fake-api.com/filters/scans/reports" - ], - "kwargs": { - "headers": { - "X-ApiKeys": "accessKey=fake_access_key; secretKey=fake_access_key", - "accept": "application/json", - "content-type": "application/json", - "User-Agent": "Integration/1.0 (PAN; Cortex-XSOAR; Build/2.0)" - }, - "verify": false - } + "GET", + "filters/scans/reports" + ] } } \ No newline at end of file From 5b383b6f68615cc4c1f1edabe2d3464d73dc6c7f Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 25 Jul 2023 22:34:25 +0300 Subject: [PATCH 29/81] update readme,yml,tests --- .../Integrations/Tenable_io/README.md | 25 ++++++---- .../Integrations/Tenable_io/Tenable_io.py | 9 +++- .../Integrations/Tenable_io/Tenable_io.yml | 35 ++++++++++---- .../Tenable_io/Tenable_io_test.py | 40 ++++++++++------ .../test_data/initiate_export_scan.json | 48 +++++++++---------- 5 files changed, 97 insertions(+), 60 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index 9219020455b2..b4486baf0323 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -32,6 +32,11 @@ This integration was integrated and tested with January 2023 release of Tenable. | tenable-io-get-asset-details | BASIC [16] user permissions. | | tenable-io-export-assets | ADMINISTRATOR [64] user permissions. | | tenable-io-export-vulnerabilities | ADMINISTRATOR [64] user permissions. | +| tenable-io-list-scan-filters | BASIC [16] user permissions | +| tenable-io-get-scan-history | SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions | +| tenable-io-export-scan | SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions | + + ## Concurrency Limits @@ -1448,9 +1453,9 @@ Lists the individual runs of the specified scan. | **Argument Name** | **Description** | **Required** | | --- | --- | --- | | scanId | The ID of the scan of which to get the runs. | Required | -| sortFields | The fields by which to sort, in the order defined by "sortOrder". Possible values are: start_date, end_date, status. | Optional | -| sortOrder | The direction in which to sort the fields defined by "sortFields". Possible values are: asc, desc. Default is asc. | Optional | -| excludeRollover | Whether to exclude rollover scans from the scan history. Default is false. Possible values are: true, false. Default is false. | Optional | +| sortFields | A comma-separated list of fields by which to sort, in the order defined by "sortOrder". Possible values are: start_date, end_date, status. | Optional | +| sortOrder | A comma-separated list of direction(s) in which to sort the fields defined by "sortFields".
If multiple directions are chosen, they will be sequentially matched with "sortFields".
If only one direction is chosen it will be used to sort all values in "sortFields".
Example 1:
sortFields: start_date,status
sortOrder: asc,desc
Result:
start_date is sorted in ascending order.
status is sorted in descending order.
Example 2:
sortFields: start_date,status
sortOrder: asc
Result:
Both start_date are status are sorted in ascending order.
. Possible values are: asc, desc. Default is asc. | Optional | +| excludeRollover | Whether to exclude rollover scans from the scan history. Possible values are: true, false. Default is false. | Optional | | page | The page number of scan records to retrieve (used for pagination) starting from 1. The page size is defined by the "pageSize" argument. | Optional | | pageSize | The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. | Optional | | limit | The maximum number of records to retrieve. If "pageSize" is defined, this argument is ignored. Default is 50. | Optional | @@ -1551,6 +1556,8 @@ Lists the individual runs of the specified scan. *** Export and download a scan report. +Scan results older than 35 days are supported in Nessus and CSV formats only. + #### Base Command @@ -1563,12 +1570,12 @@ Export and download a scan report. | scanId | The identifier for the scan to export. Run the "tenable-io-list-scans" command to get all available scans. | Required | | historyId | The unique identifier of the historical data to export. Run the "tenable-io-get-scan-history" command to get history IDs. | Optional | | historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | -| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 60 days.
For scans that are older than 60 days, only the Nessus and CSV formats are supported.
Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Required | -| chapters | A list of chapters to include in the export.
This argument is required if the file format is PDF or HTML.
. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | -| filterName | A list of filters to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | -| filterQuality | A list of operators for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | -| filterValue | A list of values for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | -| filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: and, or. Default is and. | Optional | +| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
. Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Required | +| chapters | A comma-separated list of chapters to include in the export. This argument is required if the file format is PDF or HTML. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | +| filterName | A comma-separated list of filters to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | +| filterQuality | A comma-separated list of operators for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | +| filterValue | A comma-separated list of values for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | +| filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: AND, OR. Default is AND. | Optional | | assetId | The ID of the asset scanned. | Optional | #### Context Output diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index d93ef4bdf4a8..80d415ddbbf6 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -224,6 +224,7 @@ def get_page(**page_args): class Client(BaseClient): + def list_scan_filters(self): return self._http_request( 'GET', 'filters/scans/reports') @@ -1276,8 +1277,12 @@ def scan_history_readable(history: list) -> str: def scan_history_params(args: dict) -> dict: return { 'sort': ','.join( - f'{field}:{args["sortOrder"]}' - for field in argToList(args.get('sortFields'))), + f'{field}:{order}' + for field, order + in zip( + argToList(args.get('sortFields')), + argToList(args.get('sortOrder')) * 3 + )), 'exclude_rollover': args['excludeRollover'], } | sub_dict(args, 'limit', 'offset') diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index cecb924ef858..ecdef758d658 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1118,7 +1118,7 @@ script: description: The ID of the scan of which to get the runs. required: true - name: sortFields - description: 'The fields by which to sort, in the order defined by "sortOrder".' + description: A comma-separated list of fields by which to sort, in the order defined by "sortOrder". isArray: true auto: PREDEFINED predefined: @@ -1126,7 +1126,22 @@ script: - end_date - status - name: sortOrder - description: The direction in which to sort the fields defined by "sortFields". + description: | + A comma-separated list of direction(s) in which to sort the fields defined by "sortFields". + If multiple directions are chosen, they will be sequentially matched with "sortFields". + If only one direction is chosen it will be used to sort all values in "sortFields". + Example 1: + sortFields: start_date,status + sortOrder: asc,desc + Result: + start_date is sorted in ascending order. + status is sorted in descending order. + Example 2: + sortFields: start_date,status + sortOrder: asc + Result: + Both start_date are status are sorted in ascending order. + isArray: true defaultValue: asc auto: PREDEFINED predefined: @@ -1186,8 +1201,8 @@ script: description: The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. - name: format description: | - The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 60 days. - For scans that are older than 60 days, only the Nessus and CSV formats are supported. + The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days. + For scans that are older than 35 days, only the Nessus and CSV formats are supported. required: true defaultValue: CSV auto: PREDEFINED @@ -1197,7 +1212,7 @@ script: - PDF - CSV - name: chapters - description: A list of chapters to include in the export. This argument is required if the file format is PDF or HTML. + description: A comma-separated list of chapters to include in the export. This argument is required if the file format is PDF or HTML. isArray: true auto: PREDEFINED predefined: @@ -1209,19 +1224,19 @@ script: - compliance - name: filterName description: | - A list of filters to apply to the exported scan report. + A comma-separated list of filters to apply to the exported scan report. Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response). The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. isArray: true - name: filterQuality description: | - A list of operators for the filter to apply to the exported scan report. + A comma-separated list of operators for the filter to apply to the exported scan report. Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response). The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. isArray: true - name: filterValue description: | - A list of values for the filter to apply to the exported scan report. + A comma-separated list of values for the filter to apply to the exported scan report. Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response). The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. isArray: true @@ -1235,7 +1250,9 @@ script: - name: assetId description: The ID of the asset scanned. polling: true - description: Export and download a scan report. + description: | + Export and download a scan report. + Scan results older than 35 days are supported in Nessus and CSV formats only. name: tenable-io-export-scan outputs: - contextPath: InfoFile.Size diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index bd798c01060a..01c7ee680eb8 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -509,7 +509,6 @@ def test_get_scan_history_command(mocker): -""" def test_initiate_export_scan(mocker): ''' Given: @@ -526,18 +525,20 @@ def test_initiate_export_scan(mocker): test_data = load_json('initiate_export_scan') mock_demisto(mocker) - requests_post = mocker.patch.object( + request = mocker.patch.object( BaseClient, '_http_request', return_value=test_data['response_json']) file = initiate_export_scan(test_data['args'], MOCK_CLIENT) assert file == test_data['expected_file'] - # TEMP - test_data['call_args'] = list(requests_post.call_args.args) + assert request.call_args.args == tuple(test_data['called_with']['args']) + assert request.call_args.kwargs == test_data['called_with']['kwargs'] - with open('test_data/initiate_export_scan.json', 'w') as f: - json.dump(test_data, f) - # requests_post.assert_called_with(**test_data['call_args']) + # # TEMP + # test_data['called_with']['kwargs'] = request.call_args.kwargs + # with open('test_data/initiate_export_scan.json', 'w') as f: + # json.dump(test_data, f) +""" def test_check_export_scan_status(mocker): ''' Given: @@ -593,6 +594,7 @@ def test_download_export_scan(mocker): requests_get.assert_called_with( 'http://123-fake-api.com/scans/scan_id/export/file_id/download', headers=HEADERS, verify=False) +""" @pytest.mark.parametrize( @@ -626,18 +628,13 @@ def test_export_scan_command_errors(mocker, args, get_response_json, post_respon mock_demisto(mocker) mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported') - get_response = MockResponse(get_response_json) - post_response = MockResponse(post_response_json) - mocker.patch.object(requests, 'get', return_value=get_response) - mocker.patch.object(requests, 'post', return_value=post_response) + mocker.patch.object( + BaseClient, '_http_request', return_value=test_data['response_json']) with pytest.raises(DemistoException, match=message): export_scan_command(args) -""" - - @pytest.mark.parametrize( 'paginate_args, args, embeder, expected_result, expected_call_count, fail_message', ( @@ -654,6 +651,19 @@ def test_export_scan_command_errors(mocker, args, get_response_json, post_respon 5, "@paginate did not paginate five times" ), + ( + # when limit is an exact multiple than api_limit, call multiple times: + { + "api_limit": 5, + }, + { + "limit": 20 + }, + "{}", + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], + 4, + "@paginate did not paginate four times" + ), ( # when keys_to_pages is provided, extract the pages: { @@ -726,5 +736,5 @@ def pagination_func(args): result = pagination_func(args) - assert call_count == expected_call_count assert result == expected_result, fail_message + assert call_count == expected_call_count diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json index 909c2502aecd..4d376bcf7e93 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json @@ -15,30 +15,28 @@ "file": "123456789" }, "expected_file": "123456789", - "call_args": { - "url": "http://123-fake-api.com/scans/16/export", - "params": { - "history_id": "19540157", - "history_uuid": "f7eaad37-23bd-4aac-a979-baab0e9a465b" - }, - "json": { - "format": "html", - "chapters": "compliance_exec;remediations;vuln_by_plugin", - "filter.search_type": "or", - "asset_id": "10", - "filter.0.filter": "host.id", - "filter.0.quality": "eq", - "filter.0.value": ".*", - "filter.1.filter": "plugin.attributes.bid", - "filter.1.quality": "neq", - "filter.1.value": "(.[0-9]0-9)*" - }, - "headers": { - "X-ApiKeys": "accessKey=fake_access_key; secretKey=fake_access_key", - "accept": "application/json", - "content-type": "application/json", - "User-Agent": "Integration/1.0 (PAN; Cortex-XSOAR; Build/2.0)" - }, - "verify": false + "called_with": { + "args": [ + "POST", + "scans/16/export" + ], + "kwargs": { + "params": { + "historyId": "19540157", + "historyUuid": "f7eaad37-23bd-4aac-a979-baab0e9a465b" + }, + "json_data": { + "format": "html", + "chapters": "compliance_exec;remediations;vuln_by_plugin", + "filter.search_type": "or", + "asset_id": "10", + "filter.0.filter": "host.id", + "filter.0.quality": "eq", + "filter.0.value": ".*", + "filter.1.filter": "plugin.attributes.bid", + "filter.1.quality": "neq", + "filter.1.value": "(.[0-9]0-9)*" + } + } } } \ No newline at end of file From 921067129d7dab92a2a37642f3a141dd1a8ba382 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 25 Jul 2023 22:53:59 +0300 Subject: [PATCH 30/81] update readme,yml --- .../Integrations/Tenable_io/README.md | 10 +++---- .../Integrations/Tenable_io/Tenable_io.yml | 27 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index b4486baf0323..e97b7fe08385 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1454,7 +1454,7 @@ Lists the individual runs of the specified scan. | --- | --- | --- | | scanId | The ID of the scan of which to get the runs. | Required | | sortFields | A comma-separated list of fields by which to sort, in the order defined by "sortOrder". Possible values are: start_date, end_date, status. | Optional | -| sortOrder | A comma-separated list of direction(s) in which to sort the fields defined by "sortFields".
If multiple directions are chosen, they will be sequentially matched with "sortFields".
If only one direction is chosen it will be used to sort all values in "sortFields".
Example 1:
sortFields: start_date,status
sortOrder: asc,desc
Result:
start_date is sorted in ascending order.
status is sorted in descending order.
Example 2:
sortFields: start_date,status
sortOrder: asc
Result:
Both start_date are status are sorted in ascending order.
. Possible values are: asc, desc. Default is asc. | Optional | +| sortOrder | A comma-separated list of direction(s) in which to sort the fields defined by "sortFields".
If multiple directions are chosen, they will be sequentially matched with "sortFields".
If only one direction is chosen it will be used to sort all values in "sortFields".
For example:
If sortFields is "start_date,status" and sortOrder is "asc,desc",
then start_date is sorted in ascending order and status in descending order.
If sortFields is "start_date,status" and sortOrder is simply "asc",
then both start_date and status are sorted in ascending order.
. Possible values are: asc, desc. Default is asc. | Optional | | excludeRollover | Whether to exclude rollover scans from the scan history. Possible values are: true, false. Default is false. | Optional | | page | The page number of scan records to retrieve (used for pagination) starting from 1. The page size is defined by the "pageSize" argument. | Optional | | pageSize | The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. | Optional | @@ -1572,9 +1572,9 @@ Scan results older than 35 days are supported in Nessus and CSV formats only. | historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | | format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
. Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Required | | chapters | A comma-separated list of chapters to include in the export. This argument is required if the file format is PDF or HTML. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | -| filterName | A comma-separated list of filters to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | -| filterQuality | A comma-separated list of operators for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | -| filterValue | A comma-separated list of values for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
. | Optional | +| filterName | A comma-separated list of filters to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | +| filterQuality | A comma-separated list of operators for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | +| filterValue | A comma-separated list of regular expressions for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | | filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: AND, OR. Default is AND. | Optional | | assetId | The ID of the asset scanned. | Optional | @@ -1595,4 +1595,4 @@ Scan results older than 35 days are supported in Nessus and CSV formats only. >Preparing scan report: ->Returned file: scan_16_SSE-144f3dc6-cb2d-42fc-b6cc-dd20b807735f-html.html [Download](https://example.com) \ No newline at end of file +>Returned file: scan_16_SSE-144f3dc6-cb2d-42fc-b6cc-dd20b807735f-html.html [Download](https://www.paloaltonetworks.com/cortex) \ No newline at end of file diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index ecdef758d658..a43aae91e26f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1130,17 +1130,11 @@ script: A comma-separated list of direction(s) in which to sort the fields defined by "sortFields". If multiple directions are chosen, they will be sequentially matched with "sortFields". If only one direction is chosen it will be used to sort all values in "sortFields". - Example 1: - sortFields: start_date,status - sortOrder: asc,desc - Result: - start_date is sorted in ascending order. - status is sorted in descending order. - Example 2: - sortFields: start_date,status - sortOrder: asc - Result: - Both start_date are status are sorted in ascending order. + For example: + If sortFields is "start_date,status" and sortOrder is "asc,desc", + then start_date is sorted in ascending order and status in descending order. + If sortFields is "start_date,status" and sortOrder is simply "asc", + then both start_date and status are sorted in ascending order. isArray: true defaultValue: asc auto: PREDEFINED @@ -1227,18 +1221,27 @@ script: A comma-separated list of filters to apply to the exported scan report. Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response). The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. + For example: + If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2", + then two filters are applied: "n1, op1, val1" and "n2, op2, val2". isArray: true - name: filterQuality description: | A comma-separated list of operators for the filter to apply to the exported scan report. Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response). The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. + For example: + If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2", + then two filters are applied: "n1, op1, val1" and "n2, op2, val2". isArray: true - name: filterValue description: | - A comma-separated list of values for the filter to apply to the exported scan report. + A comma-separated list of regular expressions for the filter to apply to the exported scan report. Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response). The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. + For example: + If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2", + then two filters are applied: "n1, op1, val1" and "n2, op2, val2". isArray: true - name: filterSearchType description: For multiple filters, specifies whether to use the AND or the OR logical operator. From 52dab4b594cfc80f3056bc562e52053dfe48a811 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 26 Jul 2023 00:27:48 +0300 Subject: [PATCH 31/81] tests almost complete --- .../Tenable_io/Tenable_io_test.py | 135 +++++++++++------- 1 file changed, 80 insertions(+), 55 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 01c7ee680eb8..4162d933ee3d 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -450,6 +450,64 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques assert response.raw_response == export_vulnerabilities_response +@pytest.mark.parametrize( + 'args, expected_result', + [ + # Test case 1: Empty arguments + ({}, {}), + # Test case 2: Single filter + ( + { + 'filterName': 'name', + 'filterQuality': '=', + 'filterValue': 'test', + }, + { + 'filter.0.filter': 'name', + 'filter.0.quality': '=', + 'filter.0.value': 'test', + } + ), + # Test case 3: Multiple filters + ( + { + 'filterName': 'name,description', + 'filterQuality': '=,!=', + 'filterValue': 'test,test2', + }, + { + 'filter.0.filter': 'name', + 'filter.0.quality': '=', + 'filter.0.value': 'test', + 'filter.1.filter': 'description', + 'filter.1.quality': '!=', + 'filter.1.value': 'test2', + } + ) + ] +) +def test_build_filters(args, expected_result): + """ + Given: + Case 1: Empty arguments. + Case 2: The filter lists have one value. + Case 3: The filter lists have multiple values. + + When: + - Running the tenable-io-export-scan command. + + Then: + Case 1: Return empty arguments. + Case 2: A single filter (name, quality, and value). + Case 3: Multiple filters (name, quality, and value). + """ + from Tenable_io import build_filters + + result = build_filters(args) + + assert result == expected_result + + def test_list_scan_filters_command(mocker): ''' Given: @@ -508,7 +566,6 @@ def test_get_scan_history_command(mocker): assert request.call_args.kwargs == test_data['called_with']['kwargs'] - def test_initiate_export_scan(mocker): ''' Given: @@ -533,37 +590,6 @@ def test_initiate_export_scan(mocker): assert request.call_args.args == tuple(test_data['called_with']['args']) assert request.call_args.kwargs == test_data['called_with']['kwargs'] - # # TEMP - # test_data['called_with']['kwargs'] = request.call_args.kwargs - - # with open('test_data/initiate_export_scan.json', 'w') as f: - # json.dump(test_data, f) -""" -def test_check_export_scan_status(mocker): - ''' - Given: - - A request to export a scan report. - - When: - - Running the "export-scan" command. - - Then: - - Initiate an export scan request. - ''' - from Tenable_io import check_export_scan_status, HEADERS - - mock_demisto(mocker) - requests_get = mocker.patch.object( - requests, 'get', return_value=MockResponse({'status': 'ready'})) - - file = check_export_scan_status('scan_id', 'file_id') - - assert file == 'ready' - - requests_get.assert_called_with( - 'http://123-fake-api.com/scans/scan_id/export/file_id/status', - headers=HEADERS, verify=False) - def test_download_export_scan(mocker): ''' @@ -576,13 +602,12 @@ def test_download_export_scan(mocker): Then: - Initiate an export scan request. ''' - from Tenable_io import download_export_scan, HEADERS - mock_demisto(mocker) - requests_get = mocker.patch.object( - requests, 'get', return_value=MockResponse({}, content='content')) + mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported') + request = mocker.patch.object( + BaseClient, '_http_request', return_value=b'') - result = download_export_scan('scan_id', 'file_id', {'format': 'HTML'}) + result = MOCK_CLIENT.download_export_scan('scan_id', 'file_id', 'HTML') assert result == { 'Contents': '', @@ -591,24 +616,19 @@ def test_download_export_scan(mocker): 'File': 'scan_scan_id_file_id.html', 'FileID': 'file', } - requests_get.assert_called_with( - 'http://123-fake-api.com/scans/scan_id/export/file_id/download', - headers=HEADERS, verify=False) -""" + request.assert_called_with( + 'GET', 'scans/scan_id/export/file_id/download', resp_type='content') @pytest.mark.parametrize( - 'args, get_response_json, post_response_json, message', + 'args, response_json, message', [ - ({'scanId': '', 'format': 'HTML'}, {}, {}, 'The "chapters" field must be provided for PDF or HTML formats.'), - ({'scanId': '', 'format': ''}, {'status': 'ready'}, {}, 'Unexpected response from Tenable IO: {}'), - ({'scanId': '', 'format': ''}, {'status': 'error'}, {'file': 'file_id'}, - 'Tenable IO encountered an error while exporting the scan report file.'), - ({'scanId': '', 'format': ''}, {'status': 'random'}, {'file': 'file_id'}, - 'Got unexpected status while exporting the scan report file: \'random\'') + ({'scanId': '', 'format': 'HTML'}, {}, 'The "chapters" field must be provided for PDF or HTML formats.'), + ({'scanId': '', 'format': ''}, {'status': 'error'}, + 'Tenable IO encountered an error while exporting the scan report file.') ] ) -def test_export_scan_command_errors(mocker, args, get_response_json, post_response_json, message): +def test_export_scan_command_errors(mocker, args, response_json, message): ''' Given: - A request to export a scan report. @@ -616,9 +636,7 @@ def test_export_scan_command_errors(mocker, args, get_response_json, post_respon When: - Running the "export-scan" command in any of the following cases: - Case A: The "format" arg is HTML or PDF but "chapters" is not defined. - - Case B: The "scans/{scanId}/export" endpoint returns a json without a "file" key. - - Case C: An export scan request has been made and the report status is "error". - - Case D: An export scan request has been made and the report status is unrecognized. + - Case B: An export scan request has been made and the report status is "error" or unrecognized. Then: - Return an error. @@ -628,11 +646,18 @@ def test_export_scan_command_errors(mocker, args, get_response_json, post_respon mock_demisto(mocker) mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported') - mocker.patch.object( - BaseClient, '_http_request', return_value=test_data['response_json']) + mocker.patch.object(BaseClient, '_http_request', return_value=response_json) + mock_client = Client( + MOCK_PARAMS['url'], + verify=True, + proxy=True, + ok_codes=(200,), + headers=HEADERS + ) + mock_client.initiate_export_scan = lambda *x: {'file': 'file_id'} with pytest.raises(DemistoException, match=message): - export_scan_command(args) + export_scan_command(args, mock_client) @pytest.mark.parametrize( From a0cbf96e46529785d7268cc3a087bdcea9e46681 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 26 Jul 2023 01:15:45 +0300 Subject: [PATCH 32/81] tests complete --- .../Integrations/Tenable_io/Tenable_io_test.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 4162d933ee3d..84c82e17e917 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -623,8 +623,8 @@ def test_download_export_scan(mocker): @pytest.mark.parametrize( 'args, response_json, message', [ - ({'scanId': '', 'format': 'HTML'}, {}, 'The "chapters" field must be provided for PDF or HTML formats.'), - ({'scanId': '', 'format': ''}, {'status': 'error'}, + ({'scanId': '', 'format': 'HTML', 'filterSearchType': ''}, {}, 'The "chapters" field must be provided for PDF or HTML formats.'), + ({'scanId': '', 'format': '', 'filterSearchType': ''}, {'status': 'error'}, 'Tenable IO encountered an error while exporting the scan report file.') ] ) @@ -647,17 +647,10 @@ def test_export_scan_command_errors(mocker, args, response_json, message): mock_demisto(mocker) mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported') mocker.patch.object(BaseClient, '_http_request', return_value=response_json) - mock_client = Client( - MOCK_PARAMS['url'], - verify=True, - proxy=True, - ok_codes=(200,), - headers=HEADERS - ) - mock_client.initiate_export_scan = lambda *x: {'file': 'file_id'} + mocker.patch.object(Client, 'initiate_export_scan', return_value={'file': 'file_id'}) with pytest.raises(DemistoException, match=message): - export_scan_command(args, mock_client) + export_scan_command(args, MOCK_CLIENT) @pytest.mark.parametrize( From 781ae14ae75e104da7ed57f006d837b09e740577 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 26 Jul 2023 09:45:09 +0300 Subject: [PATCH 33/81] added tests --- .../Integrations/Tenable_io/Tenable_io.py | 4 +- .../Tenable_io/Tenable_io_test.py | 83 +++++++++++++++---- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 80d415ddbbf6..733d6f2127d9 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1281,8 +1281,8 @@ def scan_history_params(args: dict) -> dict: for field, order in zip( argToList(args.get('sortFields')), - argToList(args.get('sortOrder')) * 3 - )), + # * 3 makes it apply to all sortFields even if it has only one value + argToList(args.get('sortOrder')) * 3)), 'exclude_rollover': args['excludeRollover'], } | sub_dict(args, 'limit', 'offset') diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 84c82e17e917..e11e72fb6361 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -4,7 +4,7 @@ from CommonServerPython import * import json from Tenable_io import Client, HEADERS -# type: ignore=operator +# mypy: disable-error-code="operator" MOCK_PARAMS = { 'access-key': 'fake_access_key', @@ -53,19 +53,6 @@ headers=HEADERS ) -class MockResponse: - - def __init__(self, json_data, status_code=200, content=None): - self.json_data = json_data - self.status_code = status_code - self.content = content - - def json(self): - return self.json_data - - def raise_for_status(self): - pass - def load_json(filename): with open(f'test_data/{filename}.json') as f: @@ -450,6 +437,71 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques assert response.raw_response == export_vulnerabilities_response +@pytest.mark.parametrize( + 'args, expected_result', + [ + # Test case 1: Basic input with limit and offset + ( + { + 'sortFields': '', + 'sortOrder': '', + 'excludeRollover': False, + 'limit': 10, + 'offset': 0 + }, + { + 'sort': '', + 'exclude_rollover': False, + 'limit': 10, + 'offset': 0, + } + ), + # Test case 2: Sorting by a multiple fields in ascending order + ( + { + 'sortFields': 'name,date,status', + 'sortOrder': 'asc', + 'excludeRollover': False, + }, + { + 'sort': 'name:asc,date:asc,status:asc', + 'exclude_rollover': False, + 'limit': None, + 'offset': None + } + ), + # Test case 3: Sorting by multiple fields in different orders + ( + { + 'sortFields': 'name,date,status', + 'sortOrder': 'asc,desc,asc', + 'excludeRollover': False, + }, + { + 'sort': 'name:asc,date:desc,status:asc', + 'exclude_rollover': False, + 'limit': None, + 'offset': None + } + ) + ] +) +def test_scan_history_params(args, expected_result): + """ + Test the scan_history_params function with various scenarios using pytest.mark.parametrize. + + Test cases: + 1. Test with basic input, no sorting, and include limit and offset. + 2. Test with sorting by a single field in ascending order. + 3. Test with sorting by multiple fields in different orders. + """ + from Tenable_io import scan_history_params + + result = scan_history_params(args) + + assert result == expected_result + + @pytest.mark.parametrize( 'args, expected_result', [ @@ -623,7 +675,8 @@ def test_download_export_scan(mocker): @pytest.mark.parametrize( 'args, response_json, message', [ - ({'scanId': '', 'format': 'HTML', 'filterSearchType': ''}, {}, 'The "chapters" field must be provided for PDF or HTML formats.'), + ({'scanId': '', 'format': 'HTML', 'filterSearchType': ''}, {}, + 'The "chapters" field must be provided for PDF or HTML formats.'), ({'scanId': '', 'format': '', 'filterSearchType': ''}, {'status': 'error'}, 'Tenable IO encountered an error while exporting the scan report file.') ] From af41e371ed5af45e020f2eba8d3518ec941fd49f Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 26 Jul 2023 10:00:31 +0300 Subject: [PATCH 34/81] added tests --- .../Integrations/Tenable_io/Tenable_io_test.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index e11e72fb6361..c2929352a3bf 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -444,7 +444,7 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques ( { 'sortFields': '', - 'sortOrder': '', + 'sortOrder': 'asc', 'excludeRollover': False, 'limit': 10, 'offset': 0 @@ -488,12 +488,18 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques ) def test_scan_history_params(args, expected_result): """ - Test the scan_history_params function with various scenarios using pytest.mark.parametrize. + Given: + Case 1: Only sortOrder is defined (Default). + Case 2: The sortFields has multiple values and sortOrder only one. + Case 3: Both sort lists have multiple values. - Test cases: - 1. Test with basic input, no sorting, and include limit and offset. - 2. Test with sorting by a single field in ascending order. - 3. Test with sorting by multiple fields in different orders. + When: + - Running the tenable-io-get-scan-history command. + + Then: + Case 1: Return empty sort. + Case 2: Sort all sortFields by sortOrder. + Case 3: Match sortFields and sortOrder's values by index. """ from Tenable_io import scan_history_params From 670f5445fc075637373399a7eadcf30cc18baac7 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 26 Jul 2023 11:27:16 +0300 Subject: [PATCH 35/81] added tec-docs to paginate --- .../Integrations/Tenable_io/Tenable_io.py | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 733d6f2127d9..b4fdb683a33a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -22,36 +22,60 @@ def paginate( api_limit=None, keys_to_pages=None, page_start=1): + """ + Decorator for paginating an API in a request function. + + :param limit_arg_name: The argument name in demisto.args() that specifies the number of results requested by the user. + Will be ignored if both "page" and "page_size" are defined. Default is 'limit'. + :type limit_arg_name: str + + :param page_arg_name: The argument name in demisto.args() that specifies the current page number. Default is 'page'. + :type page_arg_name: str + + :param page_size_arg_name: The argument name in demisto.args() that specifies the size of each page. Default is 'page_size'. + :type page_size_arg_name: str + + :param api_limit: The maximum limit supported by the API for a single request. Used only with "limit" when "page" and "page_size" + are not defined. Use None if there is no API limit. Default is None. + :type api_limit: int | None + + :param keys_to_pages: Key(s) to access the paginated data within the response dictionary. When the data is nested in multiple + dictionaries, use a list of keys as a path to the data. The function will return the combined pages + without surrounding dictionaries. Default is None. + :type keys_to_pages: Iterable | str | None + :param page_start: The starting page index. Default is 1. + :type page_start: int + """ def dec(func): def inner(args, *arguments, **kwargs): """ + Inserts the keys "limit" and "offset" to args according to the page, page_size and limit in args. + Args: - args (dict): command arguments (demisto.args()). - *arguments: any additional arguments to the command function. - **kwargs: additional keyword arguments to the command function. + args (dict): Command arguments (demisto.args()). + *arguments: Any additional arguments to the request function. + **kwargs: Additional keyword arguments to the request function. """ - # support for class functions keys = ( [keys_to_pages] if isinstance(keys_to_pages, str) - # could be None or list/tuple + # Could be None or list/tuple else keys_to_pages or []) def get_page(**page_args): - demisto.debug(f'{page_args=}') return dict_safe_get( func(args | page_args, *arguments, **kwargs), keys, return_type=list) + pages: list[Any] = [] try: page, page_size = map(int, map(args.get, (page_arg_name, page_size_arg_name))) limit, offset = page_size, (page - page_start) * page_size - except (ValueError, TypeError): # from conversion to int - + except (ValueError, TypeError): # From conversion to int limit, offset = int(args[limit_arg_name]), 0 if api_limit: @@ -79,6 +103,7 @@ def get_page(**page_args): return dec + FIELD_NAMES_MAP = { 'ScanType': 'Type', 'ScanStart': 'StartTime', From 56ee14206a2b242c96f67f39246c12c27bacb42a Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 26 Jul 2023 16:08:16 +0300 Subject: [PATCH 36/81] CR changes part 3 --- .../Integrations/Tenable_io/README.md | 22 ++--- .../Integrations/Tenable_io/Tenable_io.py | 95 ++++++++----------- .../Tenable_io/Tenable_io_test.py | 8 +- 3 files changed, 54 insertions(+), 71 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index e97b7fe08385..6c5784234482 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1434,10 +1434,10 @@ Lists the filtering, sorting, and pagination capabilities available for scan rec #### Human Readable Output >### Tenable IO Scan Filters ->|Filter name|Filter Readable name|Filter Control type|Filter regex|Readable regex|Filter operators|Filter group name| ->|---|---|---|---|---|---|---| ->| host.id | Asset ID | entry | [0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})* | 01234567-abcd-ef01-2345-6789abcdef01 | eq,
neq,
match,
nmatch | | ->| plugin.attributes.bid | Bugtraq ID | entry | ^[0-9]+(,[0-9]+)* | NUMBER | eq,
neq,
match,
nmatch | | +>|Filter name|Filter Readable name|Filter Control type|Filter regex|Readable regex|Filter operators| +>|---|---|---|---|---|---| +>| host.id | Asset ID | entry | [0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})* | 01234567-abcd-ef01-2345-6789abcdef01 | eq,
neq,
match,
nmatch | +>| plugin.attributes.bid | Bugtraq ID | entry | ^[0-9]+(,[0-9]+)* | NUMBER | eq,
neq,
match,
nmatch | ### tenable-io-get-scan-history @@ -1465,7 +1465,7 @@ Lists the individual runs of the specified scan. | **Path** | **Type** | **Description** | | --- | --- | --- | | TenableIO.ScanHistory.time_end | Number | The end time of the scan. | -| TenableIO.ScanHistory.scan_uuid | String | The UUID \(Universally Unique Identifier\) of the scan. | +| TenableIO.ScanHistory.scan_uuid | String | The UUID (Universally Unique Identifier) of the scan. | | TenableIO.ScanHistory.id | Number | The ID of the scan history. | | TenableIO.ScanHistory.is_archived | Boolean | Indicates whether the scan history is archived or not. | | TenableIO.ScanHistory.time_start | Number | The start time of the scan. | @@ -1545,12 +1545,12 @@ Lists the individual runs of the specified scan. #### Human Readable Output >### Tenable IO Scan History ->|History id|History uuid|Status|Is archived|Targets custom|Targets default|Visibility|Time start|Time end| ->|---|---|---|---|---|---|---|---|---| ->| 17235445 | 69a55b8e-0d52-427a-81e0-7dfe4dc6eda6 | completed | true | | false | public | 1677424566 | 1677425182 | ->| 17235342 | 2c592d52-df56-42e0-9f18-d892bdeb1e18 | completed | true | | false | public | 1677423906 | 1677424556 | ->| 17235033 | 44586b4f-1051-415c-b375-db86f6bd8c13 | completed | true | | false | public | 1677423247 | 1677423865 | ->| 17234969 | 06c12bf7-436f-489d-bb04-aae511ea9f5c | completed | true | | false | public | 1677422585 | 1677423205 | +>|History id|History uuid|Status|Is archived|Targets default|Visibility|Time start|Time end| +>|---|---|---|---|---|---|---|---| +>| 17235445 | 69a55b8e-0d52-427a-81e0-7dfe4dc6eda6 | completed | true | false | public | 1677424566 | 1677425182 | +>| 17235342 | 2c592d52-df56-42e0-9f18-d892bdeb1e18 | completed | true | false | public | 1677423906 | 1677424556 | +>| 17235033 | 44586b4f-1051-415c-b375-db86f6bd8c13 | completed | true | false | public | 1677423247 | 1677423865 | +>| 17234969 | 06c12bf7-436f-489d-bb04-aae511ea9f5c | completed | true | false | public | 1677422585 | 1677423205 | ### tenable-io-export-scan diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index b4fdb683a33a..1786383abd48 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -21,7 +21,7 @@ def paginate( page_size_arg_name='page_size', api_limit=None, keys_to_pages=None, - page_start=1): + page_start_index=1): """ Decorator for paginating an API in a request function. @@ -44,8 +44,8 @@ def paginate( without surrounding dictionaries. Default is None. :type keys_to_pages: Iterable | str | None - :param page_start: The starting page index. Default is 1. - :type page_start: int + :param page_start_index: The starting page index. Default is 1. + :type page_start_index: int """ def dec(func): def inner(args, *arguments, **kwargs): @@ -61,7 +61,7 @@ def inner(args, *arguments, **kwargs): keys = ( [keys_to_pages] if isinstance(keys_to_pages, str) - # Could be None or list/tuple + # Could be None or iterable else keys_to_pages or []) def get_page(**page_args): @@ -71,26 +71,24 @@ def get_page(**page_args): pages: list[Any] = [] - try: - page, page_size = map(int, map(args.get, (page_arg_name, page_size_arg_name))) - limit, offset = page_size, (page - page_start) * page_size + page = arg_to_number(args.get(page_arg_name)) + page_size = arg_to_number(args.get(page_size_arg_name)) + if isinstance(page, int) and isinstance(page_size, int): + limit = page_size + offset = (page - page_start_index) * page_size - except (ValueError, TypeError): # From conversion to int - limit, offset = int(args[limit_arg_name]), 0 + else: + limit = int(args[limit_arg_name]) + offset = 0 if api_limit: - pages += sum( - ( - get_page( - limit=api_limit, - offset=(offset := curr_offset)) - for curr_offset - in range(0, limit - api_limit, api_limit) - ), - [] - ) - limit = limit % api_limit or api_limit - offset += api_limit + for curr_offset in range(0, limit - api_limit, api_limit): + pages += get_page( + limit=api_limit, + offset=curr_offset) + + offset = limit - (limit % api_limit or api_limit) + limit -= offset pages += get_page( limit=limit, @@ -1300,14 +1298,17 @@ def scan_history_readable(history: list) -> str: def scan_history_params(args: dict) -> dict: + sort_fields = argToList(args.get('sortFields')) + sort_order = argToList(args.get('sortOrder')) + + if len(sort_order) == 1: + sort_order *= len(sort_fields) + return { 'sort': ','.join( f'{field}:{order}' for field, order - in zip( - argToList(args.get('sortFields')), - # * 3 makes it apply to all sortFields even if it has only one value - argToList(args.get('sortOrder')) * 3)), + in zip(sort_fields, sort_order)), 'exclude_rollover': args['excludeRollover'], } | sub_dict(args, 'limit', 'offset') @@ -1332,36 +1333,12 @@ def get_scan_history_command(*args) -> CommandResults: readable_output=scan_history_readable(history)) -def build_filters(args: dict) -> dict: - ''' - Makes filter args for the export scan endpoint by sequentially matching - the filterName, filterQuality and filterValue lists in args. - Example: - - filterName: name,description - filterQuality: =,!= - filterValue: test,test2 - - returns: - { - 'filter.0.filter': 'name', - 'filter.0.quality': '=', - 'filter.0.value': 'test', - 'filter.1.filter': 'description', - 'filter.1.quality': '!=', - 'filter.1.value': 'test2', - } - ''' - filters: dict[str, str] = {} - filter_lists = map(argToList, map(args.get, ('filterName', 'filterQuality', 'filterValue'))) - for i, (name, quality, value) in enumerate(zip(*filter_lists)): - filters |= { - f'filter.{i}.filter': name, - f'filter.{i}.quality': quality, - f'filter.{i}.value': value, - } - - return filters +def build_filter(filter: str | None, name: str) -> dict: + return { + f'filter.{i}.{name}': value + for i, value + in enumerate(argToList(filter)) + } def export_scan_body(args: dict) -> dict: @@ -1376,7 +1353,13 @@ def export_scan_body(args: dict) -> dict: 'chapters': chapters, 'filter.search_type': args['filterSearchType'].lower(), 'asset_id': args.get('assetId'), - } | build_filters(args) + } | build_filter( + args.get('filterName'), 'filter' + ) | build_filter( + args.get('filterQuality'), 'quality' + ) | build_filter( + args.get('filterValue'), 'value' + ) return body diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index c2929352a3bf..7149881dc952 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -755,10 +755,10 @@ def test_export_scan_command_errors(mocker, args, response_json, message): "@paginate did not extract the pages" ), ( - # when page_size and page are in args, don't use limit and get pages in respect to page_start: + # when page_size and page are in args, don't use limit and get pages in respect to page_start_index: { "api_limit": 1, - "page_start": 0, + "page_start_index": 0, }, { "page": 10, @@ -768,7 +768,7 @@ def test_export_scan_command_errors(mocker, args, response_json, message): "{}", [50, 51, 52, 53, 54], 1, - "@paginate used limit or did not use page_start" + "@paginate used limit or did not use page_start_index" ), ( # mixed bag: @@ -778,7 +778,7 @@ def test_export_scan_command_errors(mocker, args, response_json, message): "page_size_arg_name": 'fake_page_size', "api_limit": 5, "keys_to_pages": 'a', - "page_start": 1 + "page_start_index": 1 }, { "fake_page": 10, From 790234d3c8ad14d2f1d9642b4ccb8bc783196b63 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 26 Jul 2023 16:29:50 +0300 Subject: [PATCH 37/81] fixed tests --- .../Integrations/Tenable_io/Tenable_io.py | 4 +- .../Tenable_io/Tenable_io_test.py | 58 ------------------- 2 files changed, 2 insertions(+), 60 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 1786383abd48..6ff6815ddbc0 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1333,11 +1333,11 @@ def get_scan_history_command(*args) -> CommandResults: readable_output=scan_history_readable(history)) -def build_filter(filter: str | None, name: str) -> dict: +def build_filter(filter_list: str | None, name: str) -> dict: return { f'filter.{i}.{name}': value for i, value - in enumerate(argToList(filter)) + in enumerate(argToList(filter_list)) } diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 7149881dc952..f72d72a249ac 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -508,64 +508,6 @@ def test_scan_history_params(args, expected_result): assert result == expected_result -@pytest.mark.parametrize( - 'args, expected_result', - [ - # Test case 1: Empty arguments - ({}, {}), - # Test case 2: Single filter - ( - { - 'filterName': 'name', - 'filterQuality': '=', - 'filterValue': 'test', - }, - { - 'filter.0.filter': 'name', - 'filter.0.quality': '=', - 'filter.0.value': 'test', - } - ), - # Test case 3: Multiple filters - ( - { - 'filterName': 'name,description', - 'filterQuality': '=,!=', - 'filterValue': 'test,test2', - }, - { - 'filter.0.filter': 'name', - 'filter.0.quality': '=', - 'filter.0.value': 'test', - 'filter.1.filter': 'description', - 'filter.1.quality': '!=', - 'filter.1.value': 'test2', - } - ) - ] -) -def test_build_filters(args, expected_result): - """ - Given: - Case 1: Empty arguments. - Case 2: The filter lists have one value. - Case 3: The filter lists have multiple values. - - When: - - Running the tenable-io-export-scan command. - - Then: - Case 1: Return empty arguments. - Case 2: A single filter (name, quality, and value). - Case 3: Multiple filters (name, quality, and value). - """ - from Tenable_io import build_filters - - result = build_filters(args) - - assert result == expected_result - - def test_list_scan_filters_command(mocker): ''' Given: From dc1df378bb1423e5905d57a7d0bffab0bdfae4c3 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 26 Jul 2023 18:13:07 +0300 Subject: [PATCH 38/81] CR changes part 4 --- Packs/Tenable_io/Integrations/Tenable_io/README.md | 6 +++--- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 6 ++++-- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index 6c5784234482..f6fbf6aca0dd 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1371,8 +1371,8 @@ Lists the filtering, sorting, and pagination capabilities available for scan rec #### Input -| **Argument Name** | **Description** | **Required** | -| --- | --- | --- | +--- +There are no inputs for this command. #### Context Output @@ -1384,7 +1384,7 @@ Lists the filtering, sorting, and pagination capabilities available for scan rec | TenableIO.ScanFilter.control.regex | String | The regular expression used by the scan filter. | | TenableIO.ScanFilter.control.readable_regex | String | An example expression that the filter's regular expression would match. | | TenableIO.ScanFilter.operators | String | The operators available for the scan filter. | -| TenableIO.ScanFilter.group_name | Unknown | The group name associated with the scan filter. | +| TenableIO.ScanFilter.group_name | String | The group name associated with the scan filter. | #### Command example ```!tenable-io-list-scan-filters``` diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 6ff6815ddbc0..42f2a103ab5f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -87,8 +87,10 @@ def get_page(**page_args): limit=api_limit, offset=curr_offset) - offset = limit - (limit % api_limit or api_limit) - limit -= offset + # the remaining call can be less than OR equal the api_limit but not empty + remainder = limit % api_limit or api_limit + offset = limit - remainder + limit = remainder pages += get_page( limit=limit, diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index a43aae91e26f..19b1359c5b41 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1112,7 +1112,7 @@ script: type: String - contextPath: TenableIO.ScanFilter.group_name description: The group name associated with the scan filter. - type: Unknown + type: String - arguments: - name: scanId description: The ID of the scan of which to get the runs. From 5d70138c7d568518d319fb92299aab9b9dc21960 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 26 Jul 2023 19:01:44 +0300 Subject: [PATCH 39/81] updated docker --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 5 ++--- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 2 +- Packs/Tenable_io/ReleaseNotes/2_1_10.md | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 42f2a103ab5f..9e2186201049 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -35,8 +35,8 @@ def paginate( :param page_size_arg_name: The argument name in demisto.args() that specifies the size of each page. Default is 'page_size'. :type page_size_arg_name: str - :param api_limit: The maximum limit supported by the API for a single request. Used only with "limit" when "page" and "page_size" - are not defined. Use None if there is no API limit. Default is None. + :param api_limit: The maximum limit supported by the API for a single request. Used only with "limit" when "page" and + "page_size" are not defined. Use None if there is no API limit. Default is None. :type api_limit: int | None :param keys_to_pages: Key(s) to access the paginated data within the response dictionary. When the data is nested in multiple @@ -103,7 +103,6 @@ def get_page(**page_args): return dec - FIELD_NAMES_MAP = { 'ScanType': 'Type', 'ScanStart': 'StartTime', diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 19b1359c5b41..6aa6b9207390 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1279,7 +1279,7 @@ script: script: '-' subtype: python3 type: python - dockerimage: demisto/python3:3.10.12.65389 + dockerimage: demisto/python3:3.10.12.66339 tests: - Tenable.io test fromversion: 5.0.0 diff --git a/Packs/Tenable_io/ReleaseNotes/2_1_10.md b/Packs/Tenable_io/ReleaseNotes/2_1_10.md index 469e58ab98a2..1a19e22f677b 100644 --- a/Packs/Tenable_io/ReleaseNotes/2_1_10.md +++ b/Packs/Tenable_io/ReleaseNotes/2_1_10.md @@ -7,4 +7,4 @@ - ***tenable-io-list-scan-filters*** - ***tenable-io-get-scan-history*** - ***tenable-io-export-scan*** -- Updated the Docker image to: *demisto/python3:3.10.12.65389*. \ No newline at end of file +- Updated the Docker image to: *demisto/python3:3.10.12.66339*. \ No newline at end of file From 63440ee8e1216ec3309820713abcaddbd70b1036 Mon Sep 17 00:00:00 2001 From: Content Bot Date: Thu, 27 Jul 2023 14:50:11 +0000 Subject: [PATCH 40/81] Bump pack from version Tenable_io to 2.1.11. --- Packs/Tenable_io/ReleaseNotes/2_1_11.md | 10 ++++++++++ Packs/Tenable_io/pack_metadata.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 Packs/Tenable_io/ReleaseNotes/2_1_11.md diff --git a/Packs/Tenable_io/ReleaseNotes/2_1_11.md b/Packs/Tenable_io/ReleaseNotes/2_1_11.md new file mode 100644 index 000000000000..1a19e22f677b --- /dev/null +++ b/Packs/Tenable_io/ReleaseNotes/2_1_11.md @@ -0,0 +1,10 @@ + +#### Integrations + +##### Tenable.io + +- Added the following commands: + - ***tenable-io-list-scan-filters*** + - ***tenable-io-get-scan-history*** + - ***tenable-io-export-scan*** +- Updated the Docker image to: *demisto/python3:3.10.12.66339*. \ No newline at end of file diff --git a/Packs/Tenable_io/pack_metadata.json b/Packs/Tenable_io/pack_metadata.json index 83d54ec6cae8..2fcd1aeb9f38 100644 --- a/Packs/Tenable_io/pack_metadata.json +++ b/Packs/Tenable_io/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Tenable.io", "description": "A comprehensive asset centric solution to accurately track resources while accommodating dynamic assets such as cloud, mobile devices, containers and web applications.", "support": "xsoar", - "currentVersion": "2.1.10", + "currentVersion": "2.1.11", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", From 37a5c49e4a1f2734da56aefebc8cec6d7a3243b4 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 30 Jul 2023 10:18:46 +0300 Subject: [PATCH 41/81] reformatted paginate --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 9e2186201049..e2ea62050adc 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -82,15 +82,14 @@ def get_page(**page_args): offset = 0 if api_limit: - for curr_offset in range(0, limit - api_limit, api_limit): + for offset in range(0, limit - api_limit, api_limit): pages += get_page( limit=api_limit, - offset=curr_offset) + offset=offset) # the remaining call can be less than OR equal the api_limit but not empty - remainder = limit % api_limit or api_limit - offset = limit - remainder - limit = remainder + limit = limit % api_limit or api_limit + offset += api_limit pages += get_page( limit=limit, From 117aad51255365cec318c10b622b805ddedaf13b Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 30 Jul 2023 16:19:21 +0300 Subject: [PATCH 42/81] Demo changes --- .../Tenable_io/Integrations/Tenable_io/README.md | 8 ++++---- .../Integrations/Tenable_io/Tenable_io.py | 2 ++ .../Integrations/Tenable_io/Tenable_io.yml | 15 ++++++++++----- .../Integrations/Tenable_io/command_examples.txt | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index f6fbf6aca0dd..f1985c2831a2 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1572,9 +1572,9 @@ Scan results older than 35 days are supported in Nessus and CSV formats only. | historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | | format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
. Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Required | | chapters | A comma-separated list of chapters to include in the export. This argument is required if the file format is PDF or HTML. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | -| filterName | A comma-separated list of filters to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | -| filterQuality | A comma-separated list of operators for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | -| filterValue | A comma-separated list of regular expressions for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | +| filterName | A comma-separated list of filters to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | +| filterQuality | A comma-separated list of operators for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | +| filterValue | A comma-separated list of readable regular expressions for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter values ("Readable regex" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | | filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: AND, OR. Default is AND. | Optional | | assetId | The ID of the asset scanned. | Optional | @@ -1590,7 +1590,7 @@ Scan results older than 35 days are supported in Nessus and CSV formats only. | InfoFile.Extension | unknown | The file extension of the file. | #### Command example -```!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=or filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue=".*,(.[0-9]0-9)*" assetId=10``` +```!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=OR filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue="01234567-abcd-ef01-2345-6789abcdef01,NUMBER" assetId=10``` #### Human Readable Output >Preparing scan report: diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index e2ea62050adc..596b2bd52e68 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1389,6 +1389,8 @@ def export_scan_command(args: dict[str, Any], client: Client) -> PollResult: args.get('fileId') or initiate_export_scan(args, client)) + demisto.debug(f'{file_id=}') + match client.check_export_scan_status(scan_id, file_id).get('status'): case 'ready': return PollResult( diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 6aa6b9207390..70efd5b5bf9e 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1222,7 +1222,7 @@ script: Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response). The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. For example: - If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2", + If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2", then two filters are applied: "n1, op1, val1" and "n2, op2, val2". isArray: true - name: filterQuality @@ -1231,16 +1231,17 @@ script: Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response). The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. For example: - If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2", + If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2", then two filters are applied: "n1, op1, val1" and "n2, op2, val2". isArray: true - name: filterValue description: | - A comma-separated list of regular expressions for the filter to apply to the exported scan report. - Run the "tenable-io-list-scan-filters" command to get filter values ("Filter regex" in response). + A comma-separated list of readable regular expressions for the filter to apply to the exported scan report. + Run the "tenable-io-list-scan-filters" command to get filter values ("Readable + regex" in response). The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. For example: - If filterName is "n1,n2", filterQuality is "op1,op1" and filterValue is "val1,val2", + If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2", then two filters are applied: "n1, op1, val1" and "n2, op2, val2". isArray: true - name: filterSearchType @@ -1252,6 +1253,10 @@ script: - OR - name: assetId description: The ID of the asset scanned. + - name: fileId + hidden: true + - name: hide_polling_output + hidden: true polling: true description: | Export and download a scan report. diff --git a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt index f1f63086bc8a..f6b5ff4c3427 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt +++ b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt @@ -1,3 +1,3 @@ !tenable-io-list-scan-filters !tenable-io-get-scan-history scanId=16 excludeRollover=true sortFields=end_date,status sortOrder=desc page=2 pageSize=30 -!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=or filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue=".*,(.[0-9]0-9)*" assetId=10 \ No newline at end of file +!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=OR filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue="01234567-abcd-ef01-2345-6789abcdef01,NUMBER" assetId=10 \ No newline at end of file From ed8f5ceba778954af4937ea7c8fa8d1bf9daf481 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 30 Jul 2023 16:50:49 +0300 Subject: [PATCH 43/81] merged test playbooks --- ...ok-Tenable.io_Run_And_Export_Scan_test.yml | 279 --------------- .../playbook-Tenable.io_test.yml | 325 ++++++++++++------ 2 files changed, 225 insertions(+), 379 deletions(-) delete mode 100644 Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml deleted file mode 100644 index 3fa51e013f7b..000000000000 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml +++ /dev/null @@ -1,279 +0,0 @@ -id: Tenable.io Run And Export Scan Test -version: -1 -name: Tenable.io Run And Export Scan Test -starttaskid: "0" -tasks: - "0": - id: "0" - taskid: 3e71dac7-f071-4cce-8fd8-ea8d45cfa36e - type: start - task: - id: 3e71dac7-f071-4cce-8fd8-ea8d45cfa36e - version: -1 - name: "" - iscommand: false - brand: "" - nexttasks: - '#none#': - - "1" - separatecontext: false - continueonerrortype: "" - view: |- - { - "position": { - "x": 265, - "y": 50 - } - } - note: false - timertriggers: [] - ignoreworker: false - skipunavailable: false - quietmode: 0 - isoversize: false - isautoswitchedtoquietmode: false - "1": - id: "1" - taskid: f3496d83-f168-44f0-81b0-ed92c9daf7b7 - type: playbook - task: - id: f3496d83-f168-44f0-81b0-ed92c9daf7b7 - version: -1 - name: Tenable.io Scan - playbookName: Tenable.io Scan - type: playbook - iscommand: false - brand: "" - nexttasks: - '#none#': - - "2" - - "3" - scriptarguments: - scan-id: - simple: "16" - separatecontext: true - continueonerrortype: "" - loop: - iscommand: false - exitCondition: "" - wait: 1 - max: 100 - view: |- - { - "position": { - "x": 265, - "y": 195 - } - } - note: false - timertriggers: [] - ignoreworker: false - skipunavailable: false - quietmode: 0 - isoversize: false - isautoswitchedtoquietmode: false - "2": - id: "2" - taskid: 64853254-3f54-4a24-8195-0ae5c3a46ce8 - type: regular - task: - id: 64853254-3f54-4a24-8195-0ae5c3a46ce8 - version: -1 - name: Run get-scan-history Command - description: Lists the individual runs of the specified scan. - script: Tenable.io|||tenable-io-get-scan-history - type: regular - iscommand: true - brand: Tenable.io - nexttasks: - '#none#': - - "4" - scriptarguments: - excludeRollover: - simple: "true" - page: - simple: "2" - pageSize: - simple: "1" - scanId: - simple: "16" - sortFields: - simple: end_date,status - sortOrder: - simple: desc - separatecontext: false - continueonerrortype: "" - view: |- - { - "position": { - "x": 480, - "y": 370 - } - } - note: false - timertriggers: [] - ignoreworker: false - skipunavailable: false - quietmode: 0 - isoversize: false - isautoswitchedtoquietmode: false - "3": - id: "3" - taskid: 2bfe5482-f813-4f98-8273-e8c18a00b9bb - type: regular - task: - id: 2bfe5482-f813-4f98-8273-e8c18a00b9bb - version: -1 - name: Run list-scan-filters Command - description: Lists the filtering, sorting, and pagination capabilities available - for scan records on endpoints/commands that support them. - script: Tenable.io|||tenable-io-list-scan-filters - type: regular - iscommand: true - brand: Tenable.io - nexttasks: - '#none#': - - "4" - separatecontext: false - continueonerrortype: "" - view: |- - { - "position": { - "x": 50, - "y": 370 - } - } - note: false - timertriggers: [] - ignoreworker: false - skipunavailable: false - quietmode: 0 - isoversize: false - isautoswitchedtoquietmode: false - "4": - id: "4" - taskid: 3a5011bf-2735-4df0-880e-c238aae33516 - type: regular - task: - id: 3a5011bf-2735-4df0-880e-c238aae33516 - version: -1 - name: Run export-scan Command - description: Export and download a scan report. - script: Tenable.io|||tenable-io-export-scan - type: regular - iscommand: true - brand: Tenable.io - nexttasks: - '#none#': - - "5" - scriptarguments: - assetId: - simple: "10" - chapters: - simple: vuln_hosts_summary,remediations,compliance_exec,vuln_by_host,compliance,vuln_by_plugin - filterName: - complex: - root: TenableIO.ScanFilter.name - transformers: - - operator: slice - args: - from: - value: - simple: "1" - to: - value: - simple: "3" - filterQuality: - complex: - root: TenableIO.ScanFilter.operators - transformers: - - operator: slice - args: - from: - value: - simple: "1" - to: - value: - simple: "3" - filterSearchType: - simple: or - filterValue: - complex: - root: TenableIO.ScanFilter.control - accessor: regex - transformers: - - operator: slice - args: - from: - value: - simple: "1" - to: - value: - simple: "3" - format: - simple: HTML - historyId: - simple: ${TenableIO.ScanHistory.id} - historyUuid: - simple: ${TenableIO.ScanHistory.scan_uuid} - scanId: - simple: "16" - separatecontext: false - continueonerrortype: "" - view: |- - { - "position": { - "x": 265, - "y": 545 - } - } - note: false - timertriggers: [] - ignoreworker: false - skipunavailable: false - quietmode: 0 - isoversize: false - isautoswitchedtoquietmode: false - "5": - id: "5" - taskid: 4679396d-12de-4f96-8d83-75b8cf3d89d0 - type: title - task: - id: 4679396d-12de-4f96-8d83-75b8cf3d89d0 - version: -1 - name: Done - type: title - iscommand: false - brand: "" - separatecontext: false - continueonerrortype: "" - view: |- - { - "position": { - "x": 265, - "y": 720 - } - } - note: false - timertriggers: [] - ignoreworker: false - skipunavailable: false - quietmode: 0 - isoversize: false - isautoswitchedtoquietmode: false -view: |- - { - "linkLabelsPosition": {}, - "paper": { - "dimensions": { - "height": 735, - "width": 810, - "x": 50, - "y": 50 - } - } - } -inputs: [] -outputs: [] -fromversion: 5.0.0 -description: '' diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml index 57eaa9834561..3313f6adb6db 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml @@ -5,23 +5,23 @@ starttaskid: "0" tasks: "0": id: "0" - taskid: 3eb1d5e6-bcb1-4a56-8c2d-64454ee75da4 + taskid: c6c8a575-ac97-4c57-8985-0c8c2dbddf84 type: start task: - id: 3eb1d5e6-bcb1-4a56-8c2d-64454ee75da4 + id: c6c8a575-ac97-4c57-8985-0c8c2dbddf84 version: -1 name: "" iscommand: false brand: "" nexttasks: - "#none#": - - "4" + '#none#': + - "4" separatecontext: false continueonerrortype: "" view: |- { "position": { - "x": 50, + "x": 265, "y": 50 } } @@ -34,20 +34,20 @@ tasks: isautoswitchedtoquietmode: false "1": id: "1" - taskid: fc0ab069-67dc-4656-840e-40e061fa5975 + taskid: 02f8833a-276b-4b9e-831c-34e78e2f5ab4 type: regular task: - id: fc0ab069-67dc-4656-840e-40e061fa5975 + id: 02f8833a-276b-4b9e-831c-34e78e2f5ab4 version: -1 name: tenable-io-list-scans description: Retrive scans from the Tenable platform. - script: "|||tenable-io-list-scans" + script: '|||tenable-io-list-scans' type: regular iscommand: true brand: "" nexttasks: - "#none#": - - "2" + '#none#': + - "2" scriptarguments: retry-count: simple: "2" @@ -56,7 +56,7 @@ tasks: view: |- { "position": { - "x": 50, + "x": 265, "y": 370 } } @@ -69,22 +69,21 @@ tasks: isautoswitchedtoquietmode: false "2": id: "2" - taskid: 17b6cd73-2dc3-45e7-85d5-ce88bd5fb133 + taskid: 449437fd-8d0d-4dff-8ae2-14cd72c9fbcb type: regular task: - id: 17b6cd73-2dc3-45e7-85d5-ce88bd5fb133 + id: 449437fd-8d0d-4dff-8ae2-14cd72c9fbcb version: -1 name: tenable-io-get-scan-status - description: - "Check the status of a specific scan using its ID. The status can - hold following possible values : Running, Completed and Empty (Ready to run)." - script: "|||tenable-io-get-scan-status" + description: 'Check the status of a specific scan using its ID. The status can + hold following possible values : Running, Completed and Empty (Ready to run).' + script: '|||tenable-io-get-scan-status' type: regular iscommand: true brand: "" nexttasks: - "#none#": - - "3" + '#none#': + - "3" scriptarguments: retry-count: simple: "2" @@ -95,7 +94,7 @@ tasks: view: |- { "position": { - "x": 50, + "x": 265, "y": 545 } } @@ -108,20 +107,20 @@ tasks: isautoswitchedtoquietmode: false "3": id: "3" - taskid: 3426c644-a954-47ad-8c11-ae8ad0128a1a + taskid: 9614d1d3-fb7c-4b3e-84df-66a37dbe8893 type: regular task: - id: 3426c644-a954-47ad-8c11-ae8ad0128a1a + id: 9614d1d3-fb7c-4b3e-84df-66a37dbe8893 version: -1 name: tenable-io-get-scan-report description: Retrive scan-report for the given scan. - script: "|||tenable-io-get-scan-report" + script: '|||tenable-io-get-scan-report' type: regular iscommand: true brand: "" nexttasks: - "#none#": - - "6" + '#none#': + - "6" scriptarguments: detailed: simple: "yes" @@ -136,7 +135,7 @@ tasks: view: |- { "position": { - "x": 50, + "x": 265, "y": 720 } } @@ -149,10 +148,10 @@ tasks: isautoswitchedtoquietmode: false "4": id: "4" - taskid: c29ead08-0608-4606-8d8a-47502664aa06 + taskid: c35c7a8d-cf71-44c3-867b-95973eae02f9 type: regular task: - id: c29ead08-0608-4606-8d8a-47502664aa06 + id: c35c7a8d-cf71-44c3-867b-95973eae02f9 version: -1 name: DeleteContext description: Delete field from context @@ -161,8 +160,8 @@ tasks: iscommand: false brand: "" nexttasks: - "#none#": - - "1" + '#none#': + - "1" scriptarguments: all: simple: "yes" @@ -171,7 +170,7 @@ tasks: view: |- { "position": { - "x": 50, + "x": 265, "y": 195 } } @@ -184,20 +183,20 @@ tasks: isautoswitchedtoquietmode: false "5": id: "5" - taskid: 032d62f2-48ee-4da9-8d44-102483d55381 + taskid: 63b25736-4cf8-4558-8393-afa52547bf6f type: regular task: - id: 032d62f2-48ee-4da9-8d44-102483d55381 + id: 63b25736-4cf8-4558-8393-afa52547bf6f version: -1 name: tenable-io-get-vulnerability-details description: Retrieve details for the given vulnerability. - script: "|||tenable-io-get-vulnerability-details" + script: '|||tenable-io-get-vulnerability-details' type: regular iscommand: true brand: "" nexttasks: - "#none#": - - "7" + '#none#': + - "7" scriptarguments: retry-count: simple: "2" @@ -210,7 +209,7 @@ tasks: view: |- { "position": { - "x": 50, + "x": 265, "y": 1070 } } @@ -223,22 +222,21 @@ tasks: isautoswitchedtoquietmode: false "6": id: "6" - taskid: d100973b-5dd5-42bc-81e1-9823410df2bd + taskid: 8299cc5a-ed19-4d7a-88c3-9367b5f889f7 type: regular task: - id: d100973b-5dd5-42bc-81e1-9823410df2bd + id: 8299cc5a-ed19-4d7a-88c3-9367b5f889f7 version: -1 name: tenable-io-get-vulnerabilities-by-asset - description: - Get a list of up to 5000 of the vulnerabilities recorded for a + description: Get a list of up to 5000 of the vulnerabilities recorded for a given asset. By default, this list is sorted by vulnerability count, descending. - script: "|||tenable-io-get-vulnerabilities-by-asset" + script: '|||tenable-io-get-vulnerabilities-by-asset' type: regular iscommand: true brand: "" nexttasks: - "#none#": - - "5" + '#none#': + - "5" scriptarguments: hostname: simple: ec2-52-50-45-109.eu-west-1.compute.amazonaws.com @@ -249,7 +247,7 @@ tasks: view: |- { "position": { - "x": 50, + "x": 265, "y": 895 } } @@ -262,20 +260,20 @@ tasks: isautoswitchedtoquietmode: false "7": id: "7" - taskid: af2fd25a-6070-4fcb-8ac0-17f7600230c2 + taskid: bbbfda0b-1553-4185-817b-d11b787fb8bd type: regular task: - id: af2fd25a-6070-4fcb-8ac0-17f7600230c2 + id: bbbfda0b-1553-4185-817b-d11b787fb8bd version: -1 name: Tenable-io-export-assets description: Retrieves details for the specified asset to include custom attributes. - script: "|||tenable-io-export-assets" + script: '|||tenable-io-export-assets' type: regular iscommand: true brand: "" nexttasks: - "#none#": - - "10" + '#none#': + - "10" scriptarguments: chunkSize: simple: "500" @@ -286,7 +284,7 @@ tasks: view: |- { "position": { - "x": 50, + "x": 265, "y": 1245 } } @@ -299,10 +297,10 @@ tasks: isautoswitchedtoquietmode: false "8": id: "8" - taskid: 15d882e0-c3d6-4ff4-84a5-7b3204797396 + taskid: f5aca946-7998-4349-8406-af958323e879 type: regular task: - id: 15d882e0-c3d6-4ff4-84a5-7b3204797396 + id: f5aca946-7998-4349-8406-af958323e879 version: -1 name: Tenable-io-export-vulnerability description: Retrieves details for the specified asset to include custom attributes. @@ -311,8 +309,8 @@ tasks: iscommand: true brand: Tenable.io nexttasks: - "#none#": - - "9" + '#none#': + - "9" scriptarguments: numAssets: simple: "500" @@ -321,7 +319,7 @@ tasks: view: |- { "position": { - "x": 50, + "x": 265, "y": 1595 } } @@ -334,10 +332,10 @@ tasks: isautoswitchedtoquietmode: false "9": id: "9" - taskid: 8dc9bb01-ba06-4464-8cd2-4be0fe360fee + taskid: 5688eba5-b52a-49ea-85f4-9a4611f33454 type: condition task: - id: 8dc9bb01-ba06-4464-8cd2-4be0fe360fee + id: 5688eba5-b52a-49ea-85f4-9a4611f33454 version: -1 name: Verify vulnerabilities type: condition @@ -345,21 +343,22 @@ tasks: brand: "" nexttasks: "yes": - - "12" + - "12" + - "13" separatecontext: false conditions: - - label: "yes" - condition: - - - operator: isNotEmpty - left: - value: - simple: TenableIO.Vulnerability.asset - iscontext: true + - label: "yes" + condition: + - - operator: isNotEmpty + left: + value: + simple: TenableIO.Vulnerability.asset + iscontext: true continueonerrortype: "" view: |- { "position": { - "x": 50, + "x": 265, "y": 1770 } } @@ -372,10 +371,10 @@ tasks: isautoswitchedtoquietmode: false "10": id: "10" - taskid: afb82e34-3d4c-4307-84dc-f7e2f3f866b7 + taskid: f8b35fd8-eedd-4b54-8cf2-d62e01c0daa5 type: condition task: - id: afb82e34-3d4c-4307-84dc-f7e2f3f866b7 + id: f8b35fd8-eedd-4b54-8cf2-d62e01c0daa5 version: -1 name: Verify assets type: condition @@ -383,21 +382,21 @@ tasks: brand: "" nexttasks: "yes": - - "8" + - "8" separatecontext: false conditions: - - label: "yes" - condition: - - - operator: isNotEmpty - left: - value: - simple: TenableIO.Asset.id - iscontext: true + - label: "yes" + condition: + - - operator: isNotEmpty + left: + value: + simple: TenableIO.Asset.id + iscontext: true continueonerrortype: "" view: |- { "position": { - "x": 50, + "x": 265, "y": 1420 } } @@ -410,10 +409,10 @@ tasks: isautoswitchedtoquietmode: false "11": id: "11" - taskid: 681e360e-2a88-441d-8141-87cfb3801e5f + taskid: 71e70fae-1bd1-430b-8e1e-6ae63ed3ad91 type: title task: - id: 681e360e-2a88-441d-8141-87cfb3801e5f + id: 71e70fae-1bd1-430b-8e1e-6ae63ed3ad91 version: -1 name: Done type: title @@ -424,8 +423,8 @@ tasks: view: |- { "position": { - "x": 50, - "y": 2120 + "x": 265, + "y": 2295 } } note: false @@ -437,26 +436,35 @@ tasks: isautoswitchedtoquietmode: false "12": id: "12" - taskid: 6095f68c-243e-490f-8729-16a5f268ce9c - type: playbook + taskid: 53df8437-0b55-47ce-8cae-e6a8fb679000 + type: regular task: - id: 6095f68c-243e-490f-8729-16a5f268ce9c + id: 53df8437-0b55-47ce-8cae-e6a8fb679000 version: -1 - name: Tenable.io Run And Export Scan Test - playbookName: Tenable.io Run And Export Scan Test - type: playbook - iscommand: false - brand: "" + name: Run get-scan-history Command + description: Lists the individual runs of the specified scan. + script: Tenable.io|||tenable-io-get-scan-history + type: regular + iscommand: true + brand: Tenable.io nexttasks: - "#none#": - - "11" - separatecontext: true + '#none#': + - "14" + scriptarguments: + excludeRollover: + simple: "true" + page: + simple: "2" + pageSize: + simple: "1" + scanId: + simple: "16" + sortFields: + simple: end_date,status + sortOrder: + simple: desc + separatecontext: false continueonerrortype: "" - loop: - iscommand: false - exitCondition: "" - wait: 1 - max: 100 view: |- { "position": { @@ -471,13 +479,130 @@ tasks: quietmode: 0 isoversize: false isautoswitchedtoquietmode: false + "13": + id: "13" + taskid: d9871de6-59ae-4674-87a3-f1e466c23ca6 + type: regular + task: + id: d9871de6-59ae-4674-87a3-f1e466c23ca6 + version: -1 + name: Run list-scan-filters Command + description: Lists the filtering, sorting, and pagination capabilities available + for scan records on endpoints/commands that support them. + script: Tenable.io|||tenable-io-list-scan-filters + type: regular + iscommand: true + brand: Tenable.io + nexttasks: + '#none#': + - "14" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 480, + "y": 1945 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "14": + id: "14" + taskid: cba667e0-e859-4813-891b-c7a829e6095a + type: regular + task: + id: cba667e0-e859-4813-891b-c7a829e6095a + version: -1 + name: Run export-scan Command + description: Export and download a scan report. + script: Tenable.io|||tenable-io-export-scan + type: regular + iscommand: true + brand: Tenable.io + nexttasks: + '#none#': + - "11" + scriptarguments: + assetId: + simple: "10" + chapters: + simple: vuln_hosts_summary,remediations,compliance_exec,vuln_by_host,compliance,vuln_by_plugin + filterName: + complex: + root: TenableIO.ScanFilter.name + transformers: + - operator: slice + args: + from: + value: + simple: "1" + to: + value: + simple: "3" + filterQuality: + complex: + root: TenableIO.ScanFilter.operators + transformers: + - operator: slice + args: + from: + value: + simple: "1" + to: + value: + simple: "3" + filterSearchType: + simple: or + filterValue: + complex: + root: TenableIO.ScanFilter.control + accessor: readable_regex + transformers: + - operator: slice + args: + from: + value: + simple: "1" + to: + value: + simple: "3" + format: + simple: HTML + historyId: + simple: ${TenableIO.ScanHistory.id} + historyUuid: + simple: ${TenableIO.ScanHistory.scan_uuid} + scanId: + simple: "16" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 265, + "y": 2120 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false view: |- { "linkLabelsPosition": {}, "paper": { "dimensions": { - "height": 2135, - "width": 380, + "height": 2310, + "width": 810, "x": 50, "y": 50 } From 88ba0723244c37e8323e1b5529fce5929974cf0c Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 31 Jul 2023 15:46:12 +0300 Subject: [PATCH 44/81] Demo changes part 2 --- .../Integrations/Tenable_io/README.md | 8 +- .../Integrations/Tenable_io/Tenable_io.py | 191 +++++++----------- .../Integrations/Tenable_io/Tenable_io.yml | 12 +- .../Tenable_io/Tenable_io_test.py | 113 ++--------- .../Tenable_io/command_examples.txt | 2 +- .../test_data/initiate_export_scan.json | 10 +- 6 files changed, 111 insertions(+), 225 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index f1985c2831a2..311a5eca4936 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1371,8 +1371,10 @@ Lists the filtering, sorting, and pagination capabilities available for scan rec #### Input ---- -There are no inputs for this command. +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| limit | The maximum number of results to return. If "allResults" is defined, this argument is ignored. Default is 50. | Optional | +| allResults | Whether to retrieve all results. If true, the "limit" argument will be ignored. Possible values are: true, false. Default is false. | Optional | #### Context Output @@ -1387,7 +1389,7 @@ There are no inputs for this command. | TenableIO.ScanFilter.group_name | String | The group name associated with the scan filter. | #### Command example -```!tenable-io-list-scan-filters``` +```!tenable-io-list-scan-filters limit=2``` #### Context Example ```json { diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 596b2bd52e68..457b42e86359 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -15,93 +15,6 @@ urllib3.disable_warnings() -def paginate( - limit_arg_name='limit', - page_arg_name='page', - page_size_arg_name='page_size', - api_limit=None, - keys_to_pages=None, - page_start_index=1): - """ - Decorator for paginating an API in a request function. - - :param limit_arg_name: The argument name in demisto.args() that specifies the number of results requested by the user. - Will be ignored if both "page" and "page_size" are defined. Default is 'limit'. - :type limit_arg_name: str - - :param page_arg_name: The argument name in demisto.args() that specifies the current page number. Default is 'page'. - :type page_arg_name: str - - :param page_size_arg_name: The argument name in demisto.args() that specifies the size of each page. Default is 'page_size'. - :type page_size_arg_name: str - - :param api_limit: The maximum limit supported by the API for a single request. Used only with "limit" when "page" and - "page_size" are not defined. Use None if there is no API limit. Default is None. - :type api_limit: int | None - - :param keys_to_pages: Key(s) to access the paginated data within the response dictionary. When the data is nested in multiple - dictionaries, use a list of keys as a path to the data. The function will return the combined pages - without surrounding dictionaries. Default is None. - :type keys_to_pages: Iterable | str | None - - :param page_start_index: The starting page index. Default is 1. - :type page_start_index: int - """ - def dec(func): - def inner(args, *arguments, **kwargs): - """ - Inserts the keys "limit" and "offset" to args according to the page, page_size and limit in args. - - Args: - args (dict): Command arguments (demisto.args()). - *arguments: Any additional arguments to the request function. - **kwargs: Additional keyword arguments to the request function. - """ - - keys = ( - [keys_to_pages] - if isinstance(keys_to_pages, str) - # Could be None or iterable - else keys_to_pages or []) - - def get_page(**page_args): - return dict_safe_get( - func(args | page_args, *arguments, **kwargs), - keys, return_type=list) - - pages: list[Any] = [] - - page = arg_to_number(args.get(page_arg_name)) - page_size = arg_to_number(args.get(page_size_arg_name)) - if isinstance(page, int) and isinstance(page_size, int): - limit = page_size - offset = (page - page_start_index) * page_size - - else: - limit = int(args[limit_arg_name]) - offset = 0 - - if api_limit: - for offset in range(0, limit - api_limit, api_limit): - pages += get_page( - limit=api_limit, - offset=offset) - - # the remaining call can be less than OR equal the api_limit but not empty - limit = limit % api_limit or api_limit - offset += api_limit - - pages += get_page( - limit=limit, - offset=offset) - - return pages - - return inner - - return dec - - FIELD_NAMES_MAP = { 'ScanType': 'Type', 'ScanStart': 'StartTime', @@ -279,13 +192,6 @@ def download_export_scan(self, scan_id: str, file_id: str, file_format: str) -> EntryType.ENTRY_INFO_FILE) -def sub_dict(d: dict, *keys): - return { - key: d.get(key) - for key in keys - } - - def flatten(d): r = {} # type: ignore for v in d.values(): @@ -1297,6 +1203,22 @@ def scan_history_readable(history: list) -> str: removeNull=True) +def scan_history_pagination_params(args: dict) -> dict: + page = arg_to_number(args.get('page')) + page_size = arg_to_number(args.get('pageSize')) + if isinstance(page, int) and isinstance(page_size, int): + return { + 'limit': page_size, + 'offset': (page - 1) * page_size + } + + else: + return { + 'limit': args.get('limit', 50), + 'offset': 0 + } + + def scan_history_params(args: dict) -> dict: sort_fields = argToList(args.get('sortFields')) sort_order = argToList(args.get('sortOrder')) @@ -1310,21 +1232,15 @@ def scan_history_params(args: dict) -> dict: for field, order in zip(sort_fields, sort_order)), 'exclude_rollover': args['excludeRollover'], - } | sub_dict(args, 'limit', 'offset') + } | scan_history_pagination_params(args) + +def get_scan_history_command(args: dict[str, Any], client: Client) -> CommandResults: -@paginate( - page_size_arg_name='pageSize', - keys_to_pages='history') -def scan_history_request(args: dict[str, Any], client: Client): - return client.get_scan_history( + response_json = client.get_scan_history( args['scanId'], scan_history_params(args)) - - -def get_scan_history_command(*args) -> CommandResults: - - history = scan_history_request(*args) + history = response_json.get('history', '') return CommandResults( outputs_prefix='TenableIO.ScanHistory', @@ -1333,12 +1249,43 @@ def get_scan_history_command(*args) -> CommandResults: readable_output=scan_history_readable(history)) -def build_filter(filter_list: str | None, name: str) -> dict: - return { - f'filter.{i}.{name}': value - for i, value - in enumerate(argToList(filter_list)) - } +def build_filters(args: dict) -> dict: + ''' + Makes filter args for the export scan endpoint by sequentially matching + the filterName, filterQuality and filterValue lists in args. + Example: + filterName: name,description + filterQuality: LETTER,NUMBER + filterValue: test,test2 + returns: + { + 'filter.0.filter': 'name', + 'filter.0.quality': 'LETTER', + 'filter.0.value': 'test', + 'filter.1.filter': 'description', + 'filter.1.quality': 'NUMBER', + 'filter.1.value': 'test2', + + Raises a DemistoException if the lengths of the filter lists are not equal. + ''' + + filter_names = argToList(args.get('filterName')) + filter_qualities = argToList(args.get('filterQuality')) + filter_values = argToList(args.get('filterValue')) + + if len({len(filter_names), len(filter_qualities), len(filter_values)}) != 1: + raise DemistoException('filterName, filterQuality and filterValue must be provided with the same length.') + + result: dict = {} + zipped_filters = zip(filter_names, filter_qualities, filter_values) + for i, (name, quality, value) in enumerate(zipped_filters): + result |= { + f'filter.{i}.filter': name, + f'filter.{i}.quality': quality, + f'filter.{i}.value': value + } + + return result def export_scan_body(args: dict) -> dict: @@ -1353,20 +1300,18 @@ def export_scan_body(args: dict) -> dict: 'chapters': chapters, 'filter.search_type': args['filterSearchType'].lower(), 'asset_id': args.get('assetId'), - } | build_filter( - args.get('filterName'), 'filter' - ) | build_filter( - args.get('filterQuality'), 'quality' - ) | build_filter( - args.get('filterValue'), 'value' - ) + } | build_filters(args) + return body def initiate_export_scan(args: dict, client: Client) -> str: return client.initiate_export_scan( args['scanId'], - params=sub_dict(args, 'historyId', 'historyUuid'), + params={ + 'history_id': args.get('historyId'), + 'history_uuid': args.get('historyUuid') + }, body=export_scan_body(args) ).get('file', '') @@ -1388,10 +1333,11 @@ def export_scan_command(args: dict[str, Any], client: Client) -> PollResult: file_id = ( args.get('fileId') or initiate_export_scan(args, client)) - demisto.debug(f'{file_id=}') - match client.check_export_scan_status(scan_id, file_id).get('status'): + status_response = client.check_export_scan_status(scan_id, file_id) + demisto.debug(f'{status_response=}') + match status_response.get('status'): case 'ready': return PollResult( client.download_export_scan( @@ -1408,8 +1354,7 @@ def export_scan_command(args: dict[str, Any], client: Client) -> PollResult: 'format': args['format'], # not necessary but avoids confusion }) - case error: - demisto.debug(f'{error=}') + case _: raise DemistoException( 'Tenable IO encountered an error while exporting the scan report file.\n' f'Scan ID: {scan_id}\n' diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 70efd5b5bf9e..b622d46a07c7 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1088,7 +1088,17 @@ script: - contextPath: TenableIO.Vulnerability.indexed description: The date and time (in Unix time) when the vulnerability was indexed into Tenable.io. type: Date - - arguments: [] + - arguments: + - name: limit + defaultValue: 50 + description: The maximum number of results to return. If "pageSize" is defined, this argument is ignored. + - name: allResults + description: 'Whether to retrieve all results. If true, the "limit" argument will be ignored.' + auto: PREDEFINED + defaultValue: 'false' + predefined: + - 'false' + - 'true' description: Lists the filtering, sorting, and pagination capabilities available for scan records on endpoints/commands that support them. name: tenable-io-list-scan-filters outputs: diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index f72d72a249ac..052907cd81e1 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -3,7 +3,7 @@ from freezegun import freeze_time from CommonServerPython import * import json -from Tenable_io import Client, HEADERS +from Tenable_io import Client # mypy: disable-error-code="operator" MOCK_PARAMS = { @@ -50,7 +50,6 @@ verify=True, proxy=True, ok_codes=(200,), - headers=HEADERS ) @@ -466,8 +465,8 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques { 'sort': 'name:asc,date:asc,status:asc', 'exclude_rollover': False, - 'limit': None, - 'offset': None + 'limit': 50, + 'offset': 0 } ), # Test case 3: Sorting by multiple fields in different orders @@ -480,8 +479,8 @@ def test_export_vulnerabilities_command(mocker, args, return_value_export_reques { 'sort': 'name:asc,date:desc,status:asc', 'exclude_rollover': False, - 'limit': None, - 'offset': None + 'limit': 50, + 'offset': 0 } ) ] @@ -655,105 +654,35 @@ def test_export_scan_command_errors(mocker, args, response_json, message): @pytest.mark.parametrize( - 'paginate_args, args, embeder, expected_result, expected_call_count, fail_message', + 'args, expected_result', ( ( - # when limit is smaller than api_limit, call multiple times: - { - "api_limit": 5, - }, - { - "limit": 23 - }, - "{}", - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22], - 5, - "@paginate did not paginate five times" - ), - ( - # when limit is an exact multiple than api_limit, call multiple times: - { - "api_limit": 5, - }, - { - "limit": 20 - }, - "{}", - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], - 4, - "@paginate did not paginate four times" - ), - ( - # when keys_to_pages is provided, extract the pages: { - "keys_to_pages": ('a', 'b'), + 'page': '10', + 'pageSize': '5', + 'limit': '50' }, { - "limit": 10 - }, - "{{'a': {{'b': {} }} }}", - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - 1, - "@paginate did not extract the pages" - ), - ( - # when page_size and page are in args, don't use limit and get pages in respect to page_start_index: - { - "api_limit": 1, - "page_start_index": 0, - }, - { - "page": 10, - "page_size": 5, - "limit": 50 - }, - "{}", - [50, 51, 52, 53, 54], - 1, - "@paginate used limit or did not use page_start_index" + 'limit': 5, + 'offset': 45 + } ), ( - # mixed bag: { - "limit_arg_name": 'any', - "page_arg_name": 'fake_page', - "page_size_arg_name": 'fake_page_size', - "api_limit": 5, - "keys_to_pages": 'a', - "page_start_index": 1 + 'limit': '50', + 'page': '23' }, { - "fake_page": 10, - "fake_page_size": 5, - "limit": 50, - "page_size": 23 - }, - "{{'a': {} }}", - [45, 46, 47, 48, 49], - 1, - "@paginate failed mixed bag" + 'limit': '50', + 'offset': 0 + } ) ) ) -def test_paginate(paginate_args, args, embeder, expected_result, expected_call_count, fail_message): +def test_scan_history_pagination_params(args, expected_result): - from Tenable_io import paginate + from Tenable_io import scan_history_pagination_params - call_count = 0 + result = scan_history_pagination_params(args) - @paginate(**paginate_args) - def pagination_func(args): - nonlocal call_count - call_count += 1 - limit = int(args['limit']) - offset = int(args['offset']) - return eval(embeder.format( # noqa: PGH001 - list(range( - offset, - limit + offset - )))) - - result = pagination_func(args) - - assert result == expected_result, fail_message - assert call_count == expected_call_count + assert result == expected_result diff --git a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt index f6b5ff4c3427..6f297524c77b 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt +++ b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt @@ -1,3 +1,3 @@ -!tenable-io-list-scan-filters +!tenable-io-list-scan-filters limit=2 !tenable-io-get-scan-history scanId=16 excludeRollover=true sortFields=end_date,status sortOrder=desc page=2 pageSize=30 !tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=OR filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue="01234567-abcd-ef01-2345-6789abcdef01,NUMBER" assetId=10 \ No newline at end of file diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json index 4d376bcf7e93..4acdbc653a22 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/initiate_export_scan.json @@ -8,7 +8,7 @@ "filterSearchType": "or", "filterName": "host.id,plugin.attributes.bid", "filterQuality": "eq,neq", - "filterValue": ".*,(.[0-9]0-9)*", + "filterValue": "01234567-abcd-ef01-2345-6789abcdef01,NUMBER", "assetId": "10" }, "response_json": { @@ -22,8 +22,8 @@ ], "kwargs": { "params": { - "historyId": "19540157", - "historyUuid": "f7eaad37-23bd-4aac-a979-baab0e9a465b" + "history_id": "19540157", + "history_uuid": "f7eaad37-23bd-4aac-a979-baab0e9a465b" }, "json_data": { "format": "html", @@ -32,10 +32,10 @@ "asset_id": "10", "filter.0.filter": "host.id", "filter.0.quality": "eq", - "filter.0.value": ".*", + "filter.0.value": "01234567-abcd-ef01-2345-6789abcdef01", "filter.1.filter": "plugin.attributes.bid", "filter.1.quality": "neq", - "filter.1.value": "(.[0-9]0-9)*" + "filter.1.value": "NUMBER" } } } From 8bc87261b517c4f7e91a9dec2d36c2a98ba730b5 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 31 Jul 2023 16:13:30 +0300 Subject: [PATCH 45/81] Demo changes part 3; added args to list-filters --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 9 +++++++-- .../Integrations/Tenable_io/Tenable_io_test.py | 2 +- .../Tenable_io/test_data/list_scan_filters.json | 4 ++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 457b42e86359..f050f3e15533 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1170,11 +1170,16 @@ def scan_filters_human_readable(filters: list) -> str: removeNull=True) -def list_scan_filters_command(client: Client) -> CommandResults: +def list_scan_filters_command(args: dict[str, Any], client: Client) -> CommandResults: response_dict = client.list_scan_filters() filters = response_dict.get('filters', []) + if not argToBoolean(args['allResults']): + limit = arg_to_number( + args['limit'], arg_name='limit', required=True) + filters = filters[:limit] + return CommandResults( outputs_prefix='TenableIO.ScanFilter', outputs_key_field='name', @@ -1401,7 +1406,7 @@ def main(): # pragma: no cover elif command == 'tenable-io-export-vulnerabilities': return_results(export_vulnerabilities_command(args)) elif command == 'tenable-io-list-scan-filters': - return_results(list_scan_filters_command(client)) + return_results(list_scan_filters_command(args, client)) elif command == 'tenable-io-get-scan-history': return_results(get_scan_history_command(args, client)) elif command == 'tenable-io-export-scan': diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 052907cd81e1..5664044cb41a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -525,7 +525,7 @@ def test_list_scan_filters_command(mocker): request = mocker.patch.object(BaseClient, '_http_request', return_value=test_data['response_json']) mock_demisto(mocker) - results = list_scan_filters_command(MOCK_CLIENT) + results = list_scan_filters_command(test_data['args'], MOCK_CLIENT) assert results.outputs == test_data['outputs'] assert results.readable_output == test_data['readable_output'] diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json index 0321a72825b4..917b7e0fdde0 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json @@ -1,4 +1,8 @@ { + "args": { + "limit": 1, + "allResults": "true" + }, "response_json": { "filters": [ { From a5fdbd7fe7126bd6ce442a9ad8ec8b741ae4ab27 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 31 Jul 2023 16:41:31 +0300 Subject: [PATCH 46/81] remove args from list-filters --- Packs/Tenable_io/Integrations/Tenable_io/README.md | 8 +++----- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 9 ++------- .../Integrations/Tenable_io/Tenable_io_test.py | 2 +- .../Integrations/Tenable_io/command_examples.txt | 2 +- .../Tenable_io/test_data/list_scan_filters.json | 4 ---- 5 files changed, 7 insertions(+), 18 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index 311a5eca4936..f1985c2831a2 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1371,10 +1371,8 @@ Lists the filtering, sorting, and pagination capabilities available for scan rec #### Input -| **Argument Name** | **Description** | **Required** | -| --- | --- | --- | -| limit | The maximum number of results to return. If "allResults" is defined, this argument is ignored. Default is 50. | Optional | -| allResults | Whether to retrieve all results. If true, the "limit" argument will be ignored. Possible values are: true, false. Default is false. | Optional | +--- +There are no inputs for this command. #### Context Output @@ -1389,7 +1387,7 @@ Lists the filtering, sorting, and pagination capabilities available for scan rec | TenableIO.ScanFilter.group_name | String | The group name associated with the scan filter. | #### Command example -```!tenable-io-list-scan-filters limit=2``` +```!tenable-io-list-scan-filters``` #### Context Example ```json { diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index f050f3e15533..457b42e86359 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1170,16 +1170,11 @@ def scan_filters_human_readable(filters: list) -> str: removeNull=True) -def list_scan_filters_command(args: dict[str, Any], client: Client) -> CommandResults: +def list_scan_filters_command(client: Client) -> CommandResults: response_dict = client.list_scan_filters() filters = response_dict.get('filters', []) - if not argToBoolean(args['allResults']): - limit = arg_to_number( - args['limit'], arg_name='limit', required=True) - filters = filters[:limit] - return CommandResults( outputs_prefix='TenableIO.ScanFilter', outputs_key_field='name', @@ -1406,7 +1401,7 @@ def main(): # pragma: no cover elif command == 'tenable-io-export-vulnerabilities': return_results(export_vulnerabilities_command(args)) elif command == 'tenable-io-list-scan-filters': - return_results(list_scan_filters_command(args, client)) + return_results(list_scan_filters_command(client)) elif command == 'tenable-io-get-scan-history': return_results(get_scan_history_command(args, client)) elif command == 'tenable-io-export-scan': diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 5664044cb41a..052907cd81e1 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -525,7 +525,7 @@ def test_list_scan_filters_command(mocker): request = mocker.patch.object(BaseClient, '_http_request', return_value=test_data['response_json']) mock_demisto(mocker) - results = list_scan_filters_command(test_data['args'], MOCK_CLIENT) + results = list_scan_filters_command(MOCK_CLIENT) assert results.outputs == test_data['outputs'] assert results.readable_output == test_data['readable_output'] diff --git a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt index 6f297524c77b..f6b5ff4c3427 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt +++ b/Packs/Tenable_io/Integrations/Tenable_io/command_examples.txt @@ -1,3 +1,3 @@ -!tenable-io-list-scan-filters limit=2 +!tenable-io-list-scan-filters !tenable-io-get-scan-history scanId=16 excludeRollover=true sortFields=end_date,status sortOrder=desc page=2 pageSize=30 !tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=OR filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue="01234567-abcd-ef01-2345-6789abcdef01,NUMBER" assetId=10 \ No newline at end of file diff --git a/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json index 917b7e0fdde0..0321a72825b4 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json +++ b/Packs/Tenable_io/Integrations/Tenable_io/test_data/list_scan_filters.json @@ -1,8 +1,4 @@ { - "args": { - "limit": 1, - "allResults": "true" - }, "response_json": { "filters": [ { From 5735f9eda22a8c6539604d8032d2e27153df3b73 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 31 Jul 2023 16:50:39 +0300 Subject: [PATCH 47/81] remove args from list-filters 2 --- Packs/Tenable_io/Integrations/Tenable_io/README.md | 2 +- .../Integrations/Tenable_io/Tenable_io.yml | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index f1985c2831a2..b65d448bd98b 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1467,7 +1467,7 @@ Lists the individual runs of the specified scan. | TenableIO.ScanHistory.time_end | Number | The end time of the scan. | | TenableIO.ScanHistory.scan_uuid | String | The UUID (Universally Unique Identifier) of the scan. | | TenableIO.ScanHistory.id | Number | The ID of the scan history. | -| TenableIO.ScanHistory.is_archived | Boolean | Indicates whether the scan history is archived or not. | +| TenableIO.ScanHistory.is_archived | Boolean | Indicates whether the scan is archived or not. | | TenableIO.ScanHistory.time_start | Number | The start time of the scan. | | TenableIO.ScanHistory.visibility | String | The visibility of the scan. | | TenableIO.ScanHistory.targets.custom | Boolean | Indicates whether custom targets were used in the scan. | diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index b622d46a07c7..6d04cab90be7 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1088,17 +1088,7 @@ script: - contextPath: TenableIO.Vulnerability.indexed description: The date and time (in Unix time) when the vulnerability was indexed into Tenable.io. type: Date - - arguments: - - name: limit - defaultValue: 50 - description: The maximum number of results to return. If "pageSize" is defined, this argument is ignored. - - name: allResults - description: 'Whether to retrieve all results. If true, the "limit" argument will be ignored.' - auto: PREDEFINED - defaultValue: 'false' - predefined: - - 'false' - - 'true' + - arguments: [] description: Lists the filtering, sorting, and pagination capabilities available for scan records on endpoints/commands that support them. name: tenable-io-list-scan-filters outputs: @@ -1178,7 +1168,7 @@ script: description: The ID of the scan history. type: Number - contextPath: TenableIO.ScanHistory.is_archived - description: Indicates whether the scan history is archived or not. + description: Indicates whether the scan is archived or not. type: Boolean - contextPath: TenableIO.ScanHistory.time_start description: The start time of the scan. From fe79d98b584d5fa9a13942d20df4bb8b45495809 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 1 Aug 2023 08:23:39 +0300 Subject: [PATCH 48/81] added descriptions --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 6d04cab90be7..a24dc65d99fd 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1254,8 +1254,10 @@ script: - name: assetId description: The ID of the asset scanned. - name: fileId + description: '' hidden: true - name: hide_polling_output + description: '' hidden: true polling: true description: | From aaca6bdc899cc54e877e46257dfe79c90ea0bcf3 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 1 Aug 2023 15:11:01 +0300 Subject: [PATCH 49/81] fixed unit-tests KeyError --- .../Tenable_io/Tenable_io_test.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 052907cd81e1..e6ec3bb11e02 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -3,7 +3,6 @@ from freezegun import freeze_time from CommonServerPython import * import json -from Tenable_io import Client # mypy: disable-error-code="operator" MOCK_PARAMS = { @@ -45,12 +44,12 @@ 'VulnerabilityState': 'Resurfaced' } ] -MOCK_CLIENT = Client( - MOCK_PARAMS['url'], - verify=True, - proxy=True, - ok_codes=(200,), -) +MOCK_CLIENT_ARGS = { + 'base_url': MOCK_PARAMS['url'], + 'verify': True, + 'proxy': True, + 'ok_codes': (200,), +} def load_json(filename): @@ -518,14 +517,14 @@ def test_list_scan_filters_command(mocker): Then: - Verify that tenable-io-list-scan-filters command works as expected. ''' - from Tenable_io import list_scan_filters_command + from Tenable_io import list_scan_filters_command, Client test_data = load_json('list_scan_filters') request = mocker.patch.object(BaseClient, '_http_request', return_value=test_data['response_json']) mock_demisto(mocker) - results = list_scan_filters_command(MOCK_CLIENT) + results = list_scan_filters_command(Client(**MOCK_CLIENT_ARGS)) assert results.outputs == test_data['outputs'] assert results.readable_output == test_data['readable_output'] @@ -546,7 +545,7 @@ def test_get_scan_history_command(mocker): Then: - Verify that tenable-io-get-scan-history command works as expected. ''' - from Tenable_io import get_scan_history_command + from Tenable_io import get_scan_history_command, Client test_data = load_json('get_scan_history') @@ -554,7 +553,7 @@ def test_get_scan_history_command(mocker): BaseClient, '_http_request', return_value=test_data['response_json']) mock_demisto(mocker) - results = get_scan_history_command(test_data['args'], MOCK_CLIENT) + results = get_scan_history_command(test_data['args'], Client(**MOCK_CLIENT_ARGS)) assert results.outputs_prefix == 'TenableIO.ScanHistory' assert results.outputs_key_field == 'id' @@ -577,13 +576,13 @@ def test_initiate_export_scan(mocker): - Initiate an export scan request. ''' - from Tenable_io import initiate_export_scan + from Tenable_io import initiate_export_scan, Client test_data = load_json('initiate_export_scan') mock_demisto(mocker) request = mocker.patch.object( BaseClient, '_http_request', return_value=test_data['response_json']) - file = initiate_export_scan(test_data['args'], MOCK_CLIENT) + file = initiate_export_scan(test_data['args'], Client(**MOCK_CLIENT_ARGS)) assert file == test_data['expected_file'] assert request.call_args.args == tuple(test_data['called_with']['args']) @@ -601,12 +600,14 @@ def test_download_export_scan(mocker): Then: - Initiate an export scan request. ''' + from Tenable_io import Client + mock_demisto(mocker) mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported') request = mocker.patch.object( BaseClient, '_http_request', return_value=b'') - result = MOCK_CLIENT.download_export_scan('scan_id', 'file_id', 'HTML') + result = Client(**MOCK_CLIENT_ARGS).download_export_scan('scan_id', 'file_id', 'HTML') assert result == { 'Contents': '', @@ -642,7 +643,7 @@ def test_export_scan_command_errors(mocker, args, response_json, message): - Return an error. ''' - from Tenable_io import export_scan_command + from Tenable_io import export_scan_command, Client mock_demisto(mocker) mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported') @@ -650,7 +651,7 @@ def test_export_scan_command_errors(mocker, args, response_json, message): mocker.patch.object(Client, 'initiate_export_scan', return_value={'file': 'file_id'}) with pytest.raises(DemistoException, match=message): - export_scan_command(args, MOCK_CLIENT) + export_scan_command(args, Client(**MOCK_CLIENT_ARGS)) @pytest.mark.parametrize( From f781b1655d6987245a4b78bb8371a46c03c9f8b3 Mon Sep 17 00:00:00 2001 From: Content Bot Date: Sun, 6 Aug 2023 07:18:59 +0000 Subject: [PATCH 50/81] Bump pack from version Tenable_io to 2.1.12. --- Packs/Tenable_io/ReleaseNotes/2_1_12.md | 10 ++++++++++ Packs/Tenable_io/pack_metadata.json | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Packs/Tenable_io/ReleaseNotes/2_1_12.md diff --git a/Packs/Tenable_io/ReleaseNotes/2_1_12.md b/Packs/Tenable_io/ReleaseNotes/2_1_12.md new file mode 100644 index 000000000000..1a19e22f677b --- /dev/null +++ b/Packs/Tenable_io/ReleaseNotes/2_1_12.md @@ -0,0 +1,10 @@ + +#### Integrations + +##### Tenable.io + +- Added the following commands: + - ***tenable-io-list-scan-filters*** + - ***tenable-io-get-scan-history*** + - ***tenable-io-export-scan*** +- Updated the Docker image to: *demisto/python3:3.10.12.66339*. \ No newline at end of file diff --git a/Packs/Tenable_io/pack_metadata.json b/Packs/Tenable_io/pack_metadata.json index bd318a50572c..08fd413709b1 100644 --- a/Packs/Tenable_io/pack_metadata.json +++ b/Packs/Tenable_io/pack_metadata.json @@ -1,8 +1,8 @@ { "name": "Tenable.io", - "description": "A comprehensive asset centric solution to accurately track\u00a0resources while accommodating\u00a0dynamic assets such as cloud, mobile devices, containers and web applications.", + "description": "A comprehensive asset centric solution to accurately track resources while accommodating dynamic assets such as cloud, mobile devices, containers and web applications.", "support": "xsoar", - "currentVersion": "2.1.11", + "currentVersion": "2.1.12", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", From 3ca1e55fc4afaa7669fec037f801bb0aef1b7197 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 6 Aug 2023 13:02:03 +0300 Subject: [PATCH 51/81] Demo changes part 4; added descrptions --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 2 +- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 457b42e86359..ecb361011cf5 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1274,7 +1274,7 @@ def build_filters(args: dict) -> dict: filter_values = argToList(args.get('filterValue')) if len({len(filter_names), len(filter_qualities), len(filter_values)}) != 1: - raise DemistoException('filterName, filterQuality and filterValue must be provided with the same length.') + raise DemistoException('filterName, filterQuality and filterValue must have the same length.') result: dict = {} zipped_filters = zip(filter_names, filter_qualities, filter_values) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index a24dc65d99fd..919a365eaaf9 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1197,7 +1197,7 @@ script: description: | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days. For scans that are older than 35 days, only the Nessus and CSV formats are supported. - required: true + The "chapters" argument must be defined if the chosen format is HTML or PDF. defaultValue: CSV auto: PREDEFINED predefined: @@ -1224,6 +1224,7 @@ script: For example: If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2", then two filters are applied: "n1, op1, val1" and "n2, op2, val2". + https://example.com/ isArray: true - name: filterQuality description: | @@ -1263,6 +1264,7 @@ script: description: | Export and download a scan report. Scan results older than 35 days are supported in Nessus and CSV formats only. + Scans that are actively running cannot be exported (run "tenable-io-list-scans" to view scan statuses) name: tenable-io-export-scan outputs: - contextPath: InfoFile.Size From 055f5c1825d08395072a25f9e3da83b54f6ee409 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 6 Aug 2023 13:50:04 +0300 Subject: [PATCH 52/81] Demo changes part 4; added filter arg --- .../Integrations/Tenable_io/README.md | 9 +++--- .../Integrations/Tenable_io/Tenable_io.py | 4 +-- .../Integrations/Tenable_io/Tenable_io.yml | 31 +++---------------- 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index b65d448bd98b..8493f9897b09 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1557,6 +1557,7 @@ Lists the individual runs of the specified scan. *** Export and download a scan report. Scan results older than 35 days are supported in Nessus and CSV formats only. +Scans that are actively running cannot be exported (run "tenable-io-list-scans" to view scan statuses) #### Base Command @@ -1570,13 +1571,13 @@ Scan results older than 35 days are supported in Nessus and CSV formats only. | scanId | The identifier for the scan to export. Run the "tenable-io-list-scans" command to get all available scans. | Required | | historyId | The unique identifier of the historical data to export. Run the "tenable-io-get-scan-history" command to get history IDs. | Optional | | historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | -| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
. Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Required | +| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
The "chapters" argument must be defined if the chosen format is HTML or PDF.
. Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Optional | | chapters | A comma-separated list of chapters to include in the export. This argument is required if the file format is PDF or HTML. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | -| filterName | A comma-separated list of filters to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | -| filterQuality | A comma-separated list of operators for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | -| filterValue | A comma-separated list of readable regular expressions for the filter to apply to the exported scan report.
Run the "tenable-io-list-scan-filters" command to get filter values ("Readable regex" in response).
The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters.
For example:
If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2",
then two filters are applied: "n1, op1, val1" and "n2, op2, val2".
. | Optional | +| filter | A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report.
Example: "port.protocol eq tcp, plugin_id eq 1234567"
Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response).
. | Optional | | filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: AND, OR. Default is AND. | Optional | | assetId | The ID of the asset scanned. | Optional | +| fileId | . | Optional | +| hide_polling_output | . | Optional | #### Context Output diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index ecb361011cf5..c90d7b2ab947 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1249,7 +1249,7 @@ def get_scan_history_command(args: dict[str, Any], client: Client) -> CommandRes readable_output=scan_history_readable(history)) -def build_filters(args: dict) -> dict: +def build_filters(filter_arg: str | None) -> dict: ''' Makes filter args for the export scan endpoint by sequentially matching the filterName, filterQuality and filterValue lists in args. @@ -1300,7 +1300,7 @@ def export_scan_body(args: dict) -> dict: 'chapters': chapters, 'filter.search_type': args['filterSearchType'].lower(), 'asset_id': args.get('assetId'), - } | build_filters(args) + } | build_filters(args.get('filter')) return body diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 919a365eaaf9..29592ba4380c 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1216,34 +1216,11 @@ script: - remediations - vuln_by_plugin - compliance - - name: filterName + - name: filter description: | - A comma-separated list of filters to apply to the exported scan report. - Run the "tenable-io-list-scan-filters" command to get filter names ("Filter name" in response). - The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. - For example: - If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2", - then two filters are applied: "n1, op1, val1" and "n2, op2, val2". - https://example.com/ - isArray: true - - name: filterQuality - description: | - A comma-separated list of operators for the filter to apply to the exported scan report. - Run the "tenable-io-list-scan-filters" command to get filter qualities ("Filter operators" in response). - The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. - For example: - If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2", - then two filters are applied: "n1, op1, val1" and "n2, op2, val2". - isArray: true - - name: filterValue - description: | - A comma-separated list of readable regular expressions for the filter to apply to the exported scan report. - Run the "tenable-io-list-scan-filters" command to get filter values ("Readable - regex" in response). - The values of the "filterName", "filterQuality" and "filterValue" lists are matched sequentially to produce individual filters. - For example: - If filterName is "n1,n2", filterQuality is "op1,op2" and filterValue is "val1,val2", - then two filters are applied: "n1, op1, val1" and "n2, op2, val2". + A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report. + Example: "port.protocol eq tcp, plugin_id eq 1234567" + Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response). isArray: true - name: filterSearchType description: For multiple filters, specifies whether to use the AND or the OR logical operator. From 539eb92e047fa50c537cf6586a2c137c21c537fd Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 6 Aug 2023 15:38:21 +0300 Subject: [PATCH 53/81] Demo changes part 5; added filter arg --- .../Integrations/Tenable_io/README.md | 8 +-- .../Integrations/Tenable_io/Tenable_io.py | 65 ++++++++++--------- .../Integrations/Tenable_io/Tenable_io.yml | 1 + .../Tenable_io/Tenable_io_test.py | 10 +-- .../Tenable_io/command_examples.txt | 2 +- .../test_data/initiate_export_scan.json | 17 ++--- 6 files changed, 56 insertions(+), 47 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index 8493f9897b09..cae05d5ab533 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1571,13 +1571,11 @@ Scans that are actively running cannot be exported (run "tenable-io-list-scans" | scanId | The identifier for the scan to export. Run the "tenable-io-list-scans" command to get all available scans. | Required | | historyId | The unique identifier of the historical data to export. Run the "tenable-io-get-scan-history" command to get history IDs. | Optional | | historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | -| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
The "chapters" argument must be defined if the chosen format is HTML or PDF.
. Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Optional | +| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
The "chapters" argument must be defined if the chosen format is HTML or PDF.
Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Optional | | chapters | A comma-separated list of chapters to include in the export. This argument is required if the file format is PDF or HTML. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | -| filter | A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report.
Example: "port.protocol eq tcp, plugin_id eq 1234567"
Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response).
. | Optional | +| filter | A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report.
Example: "port.protocol eq tcp, plugin_id eq 1234567"
Note: when used literally, commas and spaces should be escaped. (i.e. "\\\\," for comma and "\\\\s" for space)
Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response). | Optional | | filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: AND, OR. Default is AND. | Optional | | assetId | The ID of the asset scanned. | Optional | -| fileId | . | Optional | -| hide_polling_output | . | Optional | #### Context Output @@ -1591,7 +1589,7 @@ Scans that are actively running cannot be exported (run "tenable-io-list-scans" | InfoFile.Extension | unknown | The file extension of the file. | #### Command example -```!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=OR filterName="host.id,plugin.attributes.bid" filterQuality=eq,neq filterValue="01234567-abcd-ef01-2345-6789abcdef01,NUMBER" assetId=10``` +```!tenable-io-export-scan scanId=16 format=HTML chapters="compliance_exec,remediations,vuln_by_plugin" historyId=19540157 historyUuid=f7eaad37-23bd-4aac-a979-baab0e9a465b filterSearchType=OR filter="port.protocol eq tcp, plugin_id eq 1234567" assetId=10``` #### Human Readable Output >Preparing scan report: diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index c90d7b2ab947..c885c69baef1 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -6,6 +6,7 @@ import traceback from datetime import datetime import urllib3 +import re import requests @@ -1249,40 +1250,46 @@ def get_scan_history_command(args: dict[str, Any], client: Client) -> CommandRes readable_output=scan_history_readable(history)) -def build_filters(filter_arg: str | None) -> dict: - ''' - Makes filter args for the export scan endpoint by sequentially matching - the filterName, filterQuality and filterValue lists in args. - Example: - filterName: name,description - filterQuality: LETTER,NUMBER - filterValue: test,test2 - returns: - { - 'filter.0.filter': 'name', - 'filter.0.quality': 'LETTER', - 'filter.0.value': 'test', - 'filter.1.filter': 'description', - 'filter.1.quality': 'NUMBER', - 'filter.1.value': 'test2', - - Raises a DemistoException if the lengths of the filter lists are not equal. - ''' +def build_filters(filters: str | None) -> dict: + """ + Build a dictionary of filter information from a filters string. - filter_names = argToList(args.get('filterName')) - filter_qualities = argToList(args.get('filterQuality')) - filter_values = argToList(args.get('filterValue')) + Args: + filters (str, optional): A string containing filters in the format "name quality value" separated by commas. + Escaped commas (\\,) are treated as literal commas. + Defaults to None. + + Returns: + dict: A dictionary where keys are in the format 'filter.i.filter', 'filter.i.quality', and 'filter.i.value', + and values correspond to the name, quality, and value of each filter component. + + Example: + filters = "name1,good,value1\\,with\\,commas name2,excellent,value2" + result = build_filters(filters) + # Output: + # { + # 'filter.0.filter': 'name1', + # 'filter.0.quality': 'good', + # 'filter.0.value': 'value1,with,commas', + # 'filter.1.filter': 'name2', + # 'filter.1.quality': 'excellent', + # 'filter.1.value': 'value2' + # } + """ + if not filters: + return {} - if len({len(filter_names), len(filter_qualities), len(filter_values)}) != 1: - raise DemistoException('filterName, filterQuality and filterValue must have the same length.') + # split by comma without escaped commas + split_filters = re.split(r'(? Date: Sun, 6 Aug 2023 16:00:45 +0300 Subject: [PATCH 54/81] Demo changes part 6; added filter arg in TPB --- .../playbook-Tenable.io_test.yml | 186 ++++++++++-------- 1 file changed, 99 insertions(+), 87 deletions(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml index 3313f6adb6db..376411abc4cd 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml @@ -5,10 +5,10 @@ starttaskid: "0" tasks: "0": id: "0" - taskid: c6c8a575-ac97-4c57-8985-0c8c2dbddf84 + taskid: f719c97e-827c-4b3a-8d90-15d4d944de7f type: start task: - id: c6c8a575-ac97-4c57-8985-0c8c2dbddf84 + id: f719c97e-827c-4b3a-8d90-15d4d944de7f version: -1 name: "" iscommand: false @@ -21,7 +21,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 50 } } @@ -34,10 +34,10 @@ tasks: isautoswitchedtoquietmode: false "1": id: "1" - taskid: 02f8833a-276b-4b9e-831c-34e78e2f5ab4 + taskid: b4be35a2-c6c2-43f0-80df-cf78f23b2921 type: regular task: - id: 02f8833a-276b-4b9e-831c-34e78e2f5ab4 + id: b4be35a2-c6c2-43f0-80df-cf78f23b2921 version: -1 name: tenable-io-list-scans description: Retrive scans from the Tenable platform. @@ -56,7 +56,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 370 } } @@ -69,10 +69,10 @@ tasks: isautoswitchedtoquietmode: false "2": id: "2" - taskid: 449437fd-8d0d-4dff-8ae2-14cd72c9fbcb + taskid: b823f1ef-0065-4698-882a-f888eac8b9aa type: regular task: - id: 449437fd-8d0d-4dff-8ae2-14cd72c9fbcb + id: b823f1ef-0065-4698-882a-f888eac8b9aa version: -1 name: tenable-io-get-scan-status description: 'Check the status of a specific scan using its ID. The status can @@ -94,7 +94,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 545 } } @@ -107,10 +107,10 @@ tasks: isautoswitchedtoquietmode: false "3": id: "3" - taskid: 9614d1d3-fb7c-4b3e-84df-66a37dbe8893 + taskid: 25d1d634-916b-49a4-85b1-f78ed2aa9f90 type: regular task: - id: 9614d1d3-fb7c-4b3e-84df-66a37dbe8893 + id: 25d1d634-916b-49a4-85b1-f78ed2aa9f90 version: -1 name: tenable-io-get-scan-report description: Retrive scan-report for the given scan. @@ -135,7 +135,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 720 } } @@ -148,10 +148,10 @@ tasks: isautoswitchedtoquietmode: false "4": id: "4" - taskid: c35c7a8d-cf71-44c3-867b-95973eae02f9 + taskid: 207d061b-ea1f-4c0e-8d49-de23521a1e8e type: regular task: - id: c35c7a8d-cf71-44c3-867b-95973eae02f9 + id: 207d061b-ea1f-4c0e-8d49-de23521a1e8e version: -1 name: DeleteContext description: Delete field from context @@ -170,7 +170,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 195 } } @@ -183,10 +183,10 @@ tasks: isautoswitchedtoquietmode: false "5": id: "5" - taskid: 63b25736-4cf8-4558-8393-afa52547bf6f + taskid: 31fc1025-789c-4d47-811b-1b9ff3b6dc23 type: regular task: - id: 63b25736-4cf8-4558-8393-afa52547bf6f + id: 31fc1025-789c-4d47-811b-1b9ff3b6dc23 version: -1 name: tenable-io-get-vulnerability-details description: Retrieve details for the given vulnerability. @@ -209,7 +209,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 1070 } } @@ -222,10 +222,10 @@ tasks: isautoswitchedtoquietmode: false "6": id: "6" - taskid: 8299cc5a-ed19-4d7a-88c3-9367b5f889f7 + taskid: 002b1bc8-8bf1-4852-8db3-50acdbf66267 type: regular task: - id: 8299cc5a-ed19-4d7a-88c3-9367b5f889f7 + id: 002b1bc8-8bf1-4852-8db3-50acdbf66267 version: -1 name: tenable-io-get-vulnerabilities-by-asset description: Get a list of up to 5000 of the vulnerabilities recorded for a @@ -247,7 +247,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 895 } } @@ -260,10 +260,10 @@ tasks: isautoswitchedtoquietmode: false "7": id: "7" - taskid: bbbfda0b-1553-4185-817b-d11b787fb8bd + taskid: c1497579-2385-45af-8b68-6187baad6227 type: regular task: - id: bbbfda0b-1553-4185-817b-d11b787fb8bd + id: c1497579-2385-45af-8b68-6187baad6227 version: -1 name: Tenable-io-export-assets description: Retrieves details for the specified asset to include custom attributes. @@ -284,7 +284,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 1245 } } @@ -297,10 +297,10 @@ tasks: isautoswitchedtoquietmode: false "8": id: "8" - taskid: f5aca946-7998-4349-8406-af958323e879 + taskid: 4d6e868c-7d5f-4d0c-80ab-9178c72a2b8b type: regular task: - id: f5aca946-7998-4349-8406-af958323e879 + id: 4d6e868c-7d5f-4d0c-80ab-9178c72a2b8b version: -1 name: Tenable-io-export-vulnerability description: Retrieves details for the specified asset to include custom attributes. @@ -319,7 +319,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 1595 } } @@ -332,10 +332,10 @@ tasks: isautoswitchedtoquietmode: false "9": id: "9" - taskid: 5688eba5-b52a-49ea-85f4-9a4611f33454 + taskid: f759ff5e-828d-48c6-8d5a-332fc4527923 type: condition task: - id: 5688eba5-b52a-49ea-85f4-9a4611f33454 + id: f759ff5e-828d-48c6-8d5a-332fc4527923 version: -1 name: Verify vulnerabilities type: condition @@ -343,7 +343,6 @@ tasks: brand: "" nexttasks: "yes": - - "12" - "13" separatecontext: false conditions: @@ -358,7 +357,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 1770 } } @@ -371,10 +370,10 @@ tasks: isautoswitchedtoquietmode: false "10": id: "10" - taskid: f8b35fd8-eedd-4b54-8cf2-d62e01c0daa5 + taskid: fcc96894-3cc8-4b31-8bd3-b08aef458cde type: condition task: - id: f8b35fd8-eedd-4b54-8cf2-d62e01c0daa5 + id: fcc96894-3cc8-4b31-8bd3-b08aef458cde version: -1 name: Verify assets type: condition @@ -396,7 +395,7 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, "y": 1420 } } @@ -409,10 +408,10 @@ tasks: isautoswitchedtoquietmode: false "11": id: "11" - taskid: 71e70fae-1bd1-430b-8e1e-6ae63ed3ad91 + taskid: f00cabbd-de80-45b2-890d-61c82f8dcabf type: title task: - id: 71e70fae-1bd1-430b-8e1e-6ae63ed3ad91 + id: f00cabbd-de80-45b2-890d-61c82f8dcabf version: -1 name: Done type: title @@ -423,8 +422,8 @@ tasks: view: |- { "position": { - "x": 265, - "y": 2295 + "x": 50, + "y": 2645 } } note: false @@ -436,10 +435,10 @@ tasks: isautoswitchedtoquietmode: false "12": id: "12" - taskid: 53df8437-0b55-47ce-8cae-e6a8fb679000 + taskid: cbe91dea-9a73-4e81-8383-f0d306b48a2d type: regular task: - id: 53df8437-0b55-47ce-8cae-e6a8fb679000 + id: cbe91dea-9a73-4e81-8383-f0d306b48a2d version: -1 name: Run get-scan-history Command description: Lists the individual runs of the specified scan. @@ -469,7 +468,7 @@ tasks: { "position": { "x": 50, - "y": 1945 + "y": 2295 } } note: false @@ -481,10 +480,10 @@ tasks: isautoswitchedtoquietmode: false "13": id: "13" - taskid: d9871de6-59ae-4674-87a3-f1e466c23ca6 + taskid: 9c7d1767-1f11-428a-8253-7105527f8083 type: regular task: - id: d9871de6-59ae-4674-87a3-f1e466c23ca6 + id: 9c7d1767-1f11-428a-8253-7105527f8083 version: -1 name: Run list-scan-filters Command description: Lists the filtering, sorting, and pagination capabilities available @@ -495,13 +494,13 @@ tasks: brand: Tenable.io nexttasks: '#none#': - - "14" + - "15" separatecontext: false continueonerrortype: "" view: |- { "position": { - "x": 480, + "x": 50, "y": 1945 } } @@ -514,10 +513,10 @@ tasks: isautoswitchedtoquietmode: false "14": id: "14" - taskid: cba667e0-e859-4813-891b-c7a829e6095a + taskid: b8129d21-b249-4745-8bac-69cc3ac08aad type: regular task: - id: cba667e0-e859-4813-891b-c7a829e6095a + id: b8129d21-b249-4745-8bac-69cc3ac08aad version: -1 name: Run export-scan Command description: Export and download a scan report. @@ -533,45 +532,10 @@ tasks: simple: "10" chapters: simple: vuln_hosts_summary,remediations,compliance_exec,vuln_by_host,compliance,vuln_by_plugin - filterName: - complex: - root: TenableIO.ScanFilter.name - transformers: - - operator: slice - args: - from: - value: - simple: "1" - to: - value: - simple: "3" - filterQuality: - complex: - root: TenableIO.ScanFilter.operators - transformers: - - operator: slice - args: - from: - value: - simple: "1" - to: - value: - simple: "3" + filter: + simple: port.protocol eq tcp, plugin_id eq 1234567 filterSearchType: simple: or - filterValue: - complex: - root: TenableIO.ScanFilter.control - accessor: readable_regex - transformers: - - operator: slice - args: - from: - value: - simple: "1" - to: - value: - simple: "3" format: simple: HTML historyId: @@ -585,7 +549,55 @@ tasks: view: |- { "position": { - "x": 265, + "x": 50, + "y": 2470 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "15": + id: "15" + taskid: fe841822-b4d5-4598-8c4d-115c7308d9be + type: condition + task: + id: fe841822-b4d5-4598-8c4d-115c7308d9be + version: -1 + name: Verify list scan filters + type: condition + iscommand: false + brand: "" + nexttasks: + "yes": + - "12" + separatecontext: false + conditions: + - label: "yes" + condition: + - - operator: isExists + left: + value: + simple: TenableIO.ScanFilter.name + iscontext: true + - - operator: isExists + left: + value: + simple: TenableIO.ScanFilter.control.readable_regex + iscontext: true + - - operator: isExists + left: + value: + simple: TenableIO.ScanFilter.operators + iscontext: true + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, "y": 2120 } } @@ -601,8 +613,8 @@ view: |- "linkLabelsPosition": {}, "paper": { "dimensions": { - "height": 2310, - "width": 810, + "height": 2660, + "width": 380, "x": 50, "y": 50 } From f381f7ee5a8c1e418fc66224b419473a5c936c42 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 6 Aug 2023 16:15:28 +0300 Subject: [PATCH 55/81] Demo changes part 7; added link for filter arg --- Packs/Tenable_io/Integrations/Tenable_io/README.md | 2 +- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index cae05d5ab533..624400a7a97a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1573,7 +1573,7 @@ Scans that are actively running cannot be exported (run "tenable-io-list-scans" | historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | | format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
The "chapters" argument must be defined if the chosen format is HTML or PDF.
Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Optional | | chapters | A comma-separated list of chapters to include in the export. This argument is required if the file format is PDF or HTML. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | -| filter | A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report.
Example: "port.protocol eq tcp, plugin_id eq 1234567"
Note: when used literally, commas and spaces should be escaped. (i.e. "\\\\," for comma and "\\\\s" for space)
Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response). | Optional | +| filter | A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report.
Example: "port.protocol eq tcp, plugin_id eq 1234567"
Note: when used literally, commas and spaces should be escaped. (i.e. "\\\\," for comma and "\\\\s" for space)
Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response).
For more information: https://developer.tenable.com/docs/scan-export-filters-tio | Optional | | filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: AND, OR. Default is AND. | Optional | | assetId | The ID of the asset scanned. | Optional | diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 3c44673944e9..75bbfad19b4a 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1222,6 +1222,7 @@ script: Example: "port.protocol eq tcp, plugin_id eq 1234567" Note: when used literally, commas and spaces should be escaped. (i.e. "\\," for comma and "\\s" for space) Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response). + For more information: https://developer.tenable.com/docs/scan-export-filters-tio isArray: true - name: filterSearchType description: For multiple filters, specifies whether to use the AND or the OR logical operator. From f76d724158a3bc164921e09369421481386b41c3 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 7 Aug 2023 00:50:54 +0300 Subject: [PATCH 56/81] updated docker --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 2 +- Packs/Tenable_io/ReleaseNotes/2_1_12.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 75bbfad19b4a..7d44066a7fc1 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1267,7 +1267,7 @@ script: script: '-' subtype: python3 type: python - dockerimage: demisto/python3:3.10.12.66339 + dockerimage: demisto/python3:3.10.12.67728 tests: - Tenable.io test fromversion: 5.0.0 diff --git a/Packs/Tenable_io/ReleaseNotes/2_1_12.md b/Packs/Tenable_io/ReleaseNotes/2_1_12.md index 1a19e22f677b..a90ad74d76ea 100644 --- a/Packs/Tenable_io/ReleaseNotes/2_1_12.md +++ b/Packs/Tenable_io/ReleaseNotes/2_1_12.md @@ -7,4 +7,4 @@ - ***tenable-io-list-scan-filters*** - ***tenable-io-get-scan-history*** - ***tenable-io-export-scan*** -- Updated the Docker image to: *demisto/python3:3.10.12.66339*. \ No newline at end of file +- Updated the Docker image to: *demisto/python3:3.10.12.67728*. \ No newline at end of file From c3396cd53856f8024715103a952d716c286d2034 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 7 Aug 2023 23:28:20 +0300 Subject: [PATCH 57/81] fixed build_filter's comment --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index c885c69baef1..ae367d4c50c5 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1256,7 +1256,7 @@ def build_filters(filters: str | None) -> dict: Args: filters (str, optional): A string containing filters in the format "name quality value" separated by commas. - Escaped commas (\\,) are treated as literal commas. + Escaped commas (\\,) and spaces (\\s) are treated as literal characters. Defaults to None. Returns: @@ -1264,14 +1264,14 @@ def build_filters(filters: str | None) -> dict: and values correspond to the name, quality, and value of each filter component. Example: - filters = "name1,good,value1\\,with\\,commas name2,excellent,value2" + filters = "name1 good value1\\,with\\,commas, name2\\swith\\sspaces excellent value2" result = build_filters(filters) # Output: # { # 'filter.0.filter': 'name1', # 'filter.0.quality': 'good', # 'filter.0.value': 'value1,with,commas', - # 'filter.1.filter': 'name2', + # 'filter.1.filter': 'name2 with spaces', # 'filter.1.quality': 'excellent', # 'filter.1.value': 'value2' # } @@ -1344,6 +1344,7 @@ def export_scan_command(args: dict[str, Any], client: Client) -> PollResult: status_response = client.check_export_scan_status(scan_id, file_id) demisto.debug(f'{status_response=}') + match status_response.get('status'): case 'ready': return PollResult( From 5f10d9418a3d32750e1e147a6517c37634b07de9 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 9 Aug 2023 12:00:16 +0300 Subject: [PATCH 58/81] improved filter parsing --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index ae367d4c50c5..d0b0a670b425 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1287,9 +1287,9 @@ def build_filters(filters: str | None) -> dict: result: dict = {} for i, (name, quality, value) in enumerate(filters): result |= { - f'filter.{i}.filter': name.replace('\\s', ' '), - f'filter.{i}.quality': quality.replace('\\s', ' '), - f'filter.{i}.value': value.replace('\\s', ' ') + f'filter.{i}.filter': re.sub(r'(? Date: Wed, 9 Aug 2023 12:52:58 +0300 Subject: [PATCH 59/81] add filter limitation to description --- .../Integrations/Tenable_io/README.md | 4 ++-- .../Integrations/Tenable_io/Tenable_io.py | 17 +++++++++++++++++ .../Integrations/Tenable_io/Tenable_io.yml | 3 ++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index 624400a7a97a..30a235000691 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1556,7 +1556,7 @@ Lists the individual runs of the specified scan. *** Export and download a scan report. -Scan results older than 35 days are supported in Nessus and CSV formats only. +Scan results older than 35 days are supported in Nessus and CSV formats only, and filters cannot be applied. Scans that are actively running cannot be exported (run "tenable-io-list-scans" to view scan statuses) @@ -1573,7 +1573,7 @@ Scans that are actively running cannot be exported (run "tenable-io-list-scans" | historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | | format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
The "chapters" argument must be defined if the chosen format is HTML or PDF.
Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Optional | | chapters | A comma-separated list of chapters to include in the export. This argument is required if the file format is PDF or HTML. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | -| filter | A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report.
Example: "port.protocol eq tcp, plugin_id eq 1234567"
Note: when used literally, commas and spaces should be escaped. (i.e. "\\\\," for comma and "\\\\s" for space)
Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response).
For more information: https://developer.tenable.com/docs/scan-export-filters-tio | Optional | +| filter | A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report.
Example: "port.protocol eq tcp, plugin_id eq 1234567"
Note: when used literally, commas and spaces should be escaped. (i.e. "\\\\," for comma and "\\\\s" for space)
Filters cannot be applied to scans older than 35 days.
Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response).
For more information: https://developer.tenable.com/docs/scan-export-filters-tio | Optional | | filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: AND, OR. Default is AND. | Optional | | assetId | The ID of the asset scanned. | Optional | diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index d0b0a670b425..d44d85621f2f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1205,6 +1205,23 @@ def scan_history_readable(history: list) -> str: def scan_history_pagination_params(args: dict) -> dict: + ''' + Generate pagination parameters for scanning history based on the given arguments. + + This function calculates the 'limit' and 'offset' parameters for pagination + based on the provided 'page' and 'pageSize' arguments. If 'page' and 'pageSize' + are valid integer values, the function returns a dictionary containing 'limit' + and 'offset' calculated accordingly. If 'page' or 'pageSize' are not valid integers, + the function falls back to using the 'limit' argument or defaults to 50 with an + 'offset' of 0. + + Args: + args (dict): The demisto.args() dictionary containing the optional arguments for pagination: 'page', 'pageSize', 'limit'. + + Returns: + dict: A dictionary containing the calculated 'limit' and 'offset' parameters + for pagination. + ''' page = arg_to_number(args.get('page')) page_size = arg_to_number(args.get('pageSize')) if isinstance(page, int) and isinstance(page_size, int): diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 7d44066a7fc1..42246b72d9b3 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1221,6 +1221,7 @@ script: A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report. Example: "port.protocol eq tcp, plugin_id eq 1234567" Note: when used literally, commas and spaces should be escaped. (i.e. "\\," for comma and "\\s" for space) + Filters cannot be applied to scans older than 35 days. Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response). For more information: https://developer.tenable.com/docs/scan-export-filters-tio isArray: true @@ -1242,7 +1243,7 @@ script: polling: true description: | Export and download a scan report. - Scan results older than 35 days are supported in Nessus and CSV formats only. + Scan results older than 35 days are supported in Nessus and CSV formats only, and filters cannot be applied. Scans that are actively running cannot be exported (run "tenable-io-list-scans" to view scan statuses) name: tenable-io-export-scan outputs: From 7ae814954736a94633eff02cbf3ef92bda1d8ce3 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Wed, 9 Aug 2023 18:11:33 +0300 Subject: [PATCH 60/81] updated docker --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 2 +- Packs/Tenable_io/ReleaseNotes/2_1_12.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 42246b72d9b3..fc97fb8f8e8c 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1268,7 +1268,7 @@ script: script: '-' subtype: python3 type: python - dockerimage: demisto/python3:3.10.12.67728 + dockerimage: demisto/python3:3.10.12.68300 tests: - Tenable.io test fromversion: 5.0.0 diff --git a/Packs/Tenable_io/ReleaseNotes/2_1_12.md b/Packs/Tenable_io/ReleaseNotes/2_1_12.md index a90ad74d76ea..4ffef0993e0e 100644 --- a/Packs/Tenable_io/ReleaseNotes/2_1_12.md +++ b/Packs/Tenable_io/ReleaseNotes/2_1_12.md @@ -7,4 +7,4 @@ - ***tenable-io-list-scan-filters*** - ***tenable-io-get-scan-history*** - ***tenable-io-export-scan*** -- Updated the Docker image to: *demisto/python3:3.10.12.67728*. \ No newline at end of file +- Updated the Docker image to: *demisto/python3:3.10.12.68300*. \ No newline at end of file From 4d78c0d067274e7a18785caa05a81dfcf36c24b9 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Thu, 10 Aug 2023 11:40:01 +0300 Subject: [PATCH 61/81] fixed scan TPB --- .../playbook-Tenable.io_Scan_Test.yml | 79 +++++++++++-------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml index 0bf91be74344..5ff245b95343 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml @@ -5,19 +5,19 @@ starttaskid: "0" tasks: "0": id: "0" - taskid: e5bcdce1-05b5-40be-8a73-c38ae3dbee9f + taskid: cce5c0b6-d002-46e7-8ec6-ec50bc150e19 type: start task: - id: e5bcdce1-05b5-40be-8a73-c38ae3dbee9f + id: cce5c0b6-d002-46e7-8ec6-ec50bc150e19 version: -1 name: "" iscommand: false brand: "" - description: '' nexttasks: '#none#': - "2" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -26,7 +26,6 @@ tasks: } } note: false - continueonerrortype: "" timertriggers: [] ignoreworker: false skipunavailable: false @@ -35,10 +34,10 @@ tasks: isautoswitchedtoquietmode: false "2": id: "2" - taskid: ed65bc4b-8534-4ee5-8eb2-9be5a3cae6d7 + taskid: 663f0dbb-894b-4073-87ff-a5e77c43a162 type: regular task: - id: ed65bc4b-8534-4ee5-8eb2-9be5a3cae6d7 + id: 663f0dbb-894b-4073-87ff-a5e77c43a162 version: -1 name: DeleteContext description: Delete field from context @@ -53,15 +52,15 @@ tasks: all: simple: "yes" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 175 + "y": 195 } } note: false - continueonerrortype: "" timertriggers: [] ignoreworker: false skipunavailable: false @@ -70,23 +69,39 @@ tasks: isautoswitchedtoquietmode: false "3": id: "3" - taskid: bc7d35a9-3cdc-4724-8bab-0a8dbe1648d3 + taskid: facabc59-d0bb-464f-8745-62ffcac89667 type: playbook task: - id: bc7d35a9-3cdc-4724-8bab-0a8dbe1648d3 + id: facabc59-d0bb-464f-8745-62ffcac89667 version: -1 name: Tenable.io Scan + description: Run a Tenable.io scan playbookName: Tenable.io Scan type: playbook iscommand: false brand: "" - description: '' scriptarguments: - retry-count: - simple: "2" scan-id: - simple: "16" + complex: + root: TenableIO.Scan + filters: + - - operator: isEqualString + left: + value: + simple: TenableIO.Scan.Status + iscontext: true + right: + value: + simple: completed + accessor: Id + transformers: + - operator: atIndex + args: + index: + value: + simple: "0" separatecontext: true + continueonerrortype: "" loop: iscommand: false exitCondition: "" @@ -96,11 +111,10 @@ tasks: { "position": { "x": 50, - "y": 650 + "y": 720 } } note: false - continueonerrortype: "" timertriggers: [] ignoreworker: false skipunavailable: false @@ -109,10 +123,10 @@ tasks: isautoswitchedtoquietmode: false "4": id: "4" - taskid: 6174091e-cfc1-4fc4-87ea-115a501e6c53 + taskid: 49ba907d-1814-40b1-8d34-c27a4f488de1 type: playbook task: - id: 6174091e-cfc1-4fc4-87ea-115a501e6c53 + id: 49ba907d-1814-40b1-8d34-c27a4f488de1 version: -1 name: GenericPolling description: |- @@ -133,16 +147,14 @@ tasks: scriptarguments: Ids: complex: - root: TenableIO.Scan.Id - filters: - - - operator: containsGeneral - left: - value: - simple: TenableIO.Scan.Id - iscontext: true - right: + root: TenableIO.Scan + accessor: Id + transformers: + - operator: atIndex + args: + index: value: - simple: "16" + simple: "0" Interval: simple: "1" PollingCommandArgName: @@ -152,7 +164,8 @@ tasks: Timeout: simple: "10" dt: - simple: TenableIO.Scan(val.Status == 'pending' || val.Status == 'running' || val.Status == 'publishing').Id + simple: | + TenableIO.Scan(val.Status == 'pending' || val.Status == 'running' || val.Status == 'publishing').Id separatecontext: true continueonerrortype: "" loop: @@ -164,7 +177,7 @@ tasks: { "position": { "x": 50, - "y": 490 + "y": 545 } } note: false @@ -176,10 +189,10 @@ tasks: isautoswitchedtoquietmode: false "5": id: "5" - taskid: 906f1daf-f844-4d7c-884a-8892b71ca63e + taskid: 733425c5-96e1-4ddb-8cc1-f43fe6de828e type: regular task: - id: 906f1daf-f844-4d7c-884a-8892b71ca63e + id: 733425c5-96e1-4ddb-8cc1-f43fe6de828e version: -1 name: 'tenable.io list scans ' description: Retrieves scans from the Tenable platform. @@ -196,7 +209,7 @@ tasks: { "position": { "x": 50, - "y": 340 + "y": 370 } } note: false @@ -211,7 +224,7 @@ view: |- "linkLabelsPosition": {}, "paper": { "dimensions": { - "height": 695, + "height": 765, "width": 380, "x": 50, "y": 50 From 0b74143d5ae4161486c750b2a6bfeb92c64880e5 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Thu, 10 Aug 2023 16:52:41 +0300 Subject: [PATCH 62/81] fixed tpb --- .../playbook-Tenable.io_test.yml | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml index 376411abc4cd..dfef6ff6ab8f 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml @@ -435,10 +435,10 @@ tasks: isautoswitchedtoquietmode: false "12": id: "12" - taskid: cbe91dea-9a73-4e81-8383-f0d306b48a2d + taskid: ee1b63da-40f0-4102-86a2-cf36557f9c89 type: regular task: - id: cbe91dea-9a73-4e81-8383-f0d306b48a2d + id: ee1b63da-40f0-4102-86a2-cf36557f9c89 version: -1 name: Run get-scan-history Command description: Lists the individual runs of the specified scan. @@ -452,12 +452,27 @@ tasks: scriptarguments: excludeRollover: simple: "true" - page: - simple: "2" - pageSize: + limit: simple: "1" scanId: - simple: "16" + complex: + root: TenableIO.Scan + filters: + - - operator: isEqualString + left: + value: + simple: TenableIO.Scan.Status + iscontext: true + right: + value: + simple: completed + accessor: Id + transformers: + - operator: atIndex + args: + index: + value: + simple: "0" sortFields: simple: end_date,status sortOrder: @@ -513,10 +528,10 @@ tasks: isautoswitchedtoquietmode: false "14": id: "14" - taskid: b8129d21-b249-4745-8bac-69cc3ac08aad + taskid: 0883f54e-9123-4368-835d-f6223420de1e type: regular task: - id: b8129d21-b249-4745-8bac-69cc3ac08aad + id: 0883f54e-9123-4368-835d-f6223420de1e version: -1 name: Run export-scan Command description: Export and download a scan report. @@ -528,22 +543,33 @@ tasks: '#none#': - "11" scriptarguments: - assetId: - simple: "10" chapters: simple: vuln_hosts_summary,remediations,compliance_exec,vuln_by_host,compliance,vuln_by_plugin - filter: - simple: port.protocol eq tcp, plugin_id eq 1234567 - filterSearchType: - simple: or format: - simple: HTML + simple: CSV historyId: simple: ${TenableIO.ScanHistory.id} historyUuid: simple: ${TenableIO.ScanHistory.scan_uuid} scanId: - simple: "16" + complex: + root: TenableIO.Scan + filters: + - - operator: isEqualString + left: + value: + simple: TenableIO.Scan.Status + iscontext: true + right: + value: + simple: completed + accessor: Id + transformers: + - operator: atIndex + args: + index: + value: + simple: "0" separatecontext: false continueonerrortype: "" view: |- @@ -623,4 +649,4 @@ view: |- inputs: [] outputs: [] fromversion: 5.0.0 -description: "" +description: '' From 81b59d166cfb821cb329e1ae759fe6bc34e6d6fb Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 13 Aug 2023 10:09:43 +0300 Subject: [PATCH 63/81] removed flaky checks from TPB --- .../playbook-Tenable.io_Scan_Test.yml | 70 +------------------ 1 file changed, 2 insertions(+), 68 deletions(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml index 5ff245b95343..f0f264b8bf35 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml @@ -107,72 +107,6 @@ tasks: exitCondition: "" wait: 1 max: 0 - view: |- - { - "position": { - "x": 50, - "y": 720 - } - } - note: false - timertriggers: [] - ignoreworker: false - skipunavailable: false - quietmode: 0 - isoversize: false - isautoswitchedtoquietmode: false - "4": - id: "4" - taskid: 49ba907d-1814-40b1-8d34-c27a4f488de1 - type: playbook - task: - id: 49ba907d-1814-40b1-8d34-c27a4f488de1 - version: -1 - name: GenericPolling - description: |- - Use this playbook as a sub-playbook to block execution of the master playbook until a remote action is complete. - This playbook implements polling by continuously running the command in Step \#2 until the operation completes. - The remote action should have the following structure: - - 1. Initiate the operation. - 2. Poll to check if the operation completed. - 3. (optional) Get the results of the operation. - playbookName: GenericPolling - type: playbook - iscommand: false - brand: "" - nexttasks: - '#none#': - - "3" - scriptarguments: - Ids: - complex: - root: TenableIO.Scan - accessor: Id - transformers: - - operator: atIndex - args: - index: - value: - simple: "0" - Interval: - simple: "1" - PollingCommandArgName: - simple: scanId - PollingCommandName: - simple: tenable-io-get-scan-status - Timeout: - simple: "10" - dt: - simple: | - TenableIO.Scan(val.Status == 'pending' || val.Status == 'running' || val.Status == 'publishing').Id - separatecontext: true - continueonerrortype: "" - loop: - iscommand: false - exitCondition: "" - wait: 1 - max: 100 view: |- { "position": { @@ -202,7 +136,7 @@ tasks: brand: "" nexttasks: '#none#': - - "4" + - "3" separatecontext: false continueonerrortype: "" view: |- @@ -224,7 +158,7 @@ view: |- "linkLabelsPosition": {}, "paper": { "dimensions": { - "height": 765, + "height": 590, "width": 380, "x": 50, "y": 50 From 7e951574fb3f5589b5f1d7a75297c8cda2a34b99 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 13 Aug 2023 13:12:29 +0300 Subject: [PATCH 64/81] updated docker --- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 2 +- Packs/Tenable_io/ReleaseNotes/2_1_12.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index fc97fb8f8e8c..9a4e98032631 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1268,7 +1268,7 @@ script: script: '-' subtype: python3 type: python - dockerimage: demisto/python3:3.10.12.68300 + dockerimage: demisto/python3:3.10.12.68714 tests: - Tenable.io test fromversion: 5.0.0 diff --git a/Packs/Tenable_io/ReleaseNotes/2_1_12.md b/Packs/Tenable_io/ReleaseNotes/2_1_12.md index 4ffef0993e0e..28f2cf42a169 100644 --- a/Packs/Tenable_io/ReleaseNotes/2_1_12.md +++ b/Packs/Tenable_io/ReleaseNotes/2_1_12.md @@ -7,4 +7,4 @@ - ***tenable-io-list-scan-filters*** - ***tenable-io-get-scan-history*** - ***tenable-io-export-scan*** -- Updated the Docker image to: *demisto/python3:3.10.12.68300*. \ No newline at end of file +- Updated the Docker image to: *demisto/python3:3.10.12.68714*. \ No newline at end of file From 88a30b67fb9888984b756079c4323dd9f0630da5 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 13 Aug 2023 13:13:03 +0300 Subject: [PATCH 65/81] test: unmock TPBs --- Tests/conf.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/conf.json b/Tests/conf.json index 0765619ac7eb..1a9605e0668a 100644 --- a/Tests/conf.json +++ b/Tests/conf.json @@ -1208,7 +1208,8 @@ }, { "integrations": "Tenable.io", - "playbookID": "Tenable.io test" + "playbookID": "Tenable.io test", + "is_mockable": false }, { "playbookID": "URLDecode-Test" @@ -1222,6 +1223,7 @@ { "integrations": "Tenable.io", "playbookID": "Tenable.io Scan Test", + "is_mockable": false, "timeout": 3600 }, { From 514ec704275e28aa38a97da6d8a2dd139ae1cc95 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 13 Aug 2023 13:20:20 +0300 Subject: [PATCH 66/81] removed new TPB --- Utils/tests/id_set.json | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Utils/tests/id_set.json b/Utils/tests/id_set.json index 163dc1a956c3..afc006775334 100644 --- a/Utils/tests/id_set.json +++ b/Utils/tests/id_set.json @@ -40241,22 +40241,6 @@ "pack": "Tenable_io" } }, - { - "Tenable.io Run And Export Scan Test": { - "name": "Tenable.io Run And Export Scan Test", - "file_path": "Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Run_And_Export_Scan_test.yml", - "fromversion": "5.0.0", - "implementing_playbooks": [ - "Tenable.io Scan" - ], - "command_to_integration": { - "tenable-io-list-scan-filters": "", - "tenable-io-get-scan-history": "", - "tenable-io-export-scan": "" - }, - "pack": "Tenable_io" - } - }, { "Test - Cofense Intelligence": { "name": "Test - Cofense Intelligence", From 35f39c9761c3457f857a8972ae8df8220d7dd0ec Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Sun, 13 Aug 2023 14:44:18 +0300 Subject: [PATCH 67/81] revert: 'test: unmock TPBs' --- Tests/conf.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/conf.json b/Tests/conf.json index 1a9605e0668a..0765619ac7eb 100644 --- a/Tests/conf.json +++ b/Tests/conf.json @@ -1208,8 +1208,7 @@ }, { "integrations": "Tenable.io", - "playbookID": "Tenable.io test", - "is_mockable": false + "playbookID": "Tenable.io test" }, { "playbookID": "URLDecode-Test" @@ -1223,7 +1222,6 @@ { "integrations": "Tenable.io", "playbookID": "Tenable.io Scan Test", - "is_mockable": false, "timeout": 3600 }, { From 4f13fcf40a0faa4988e95b3255dc4df31c0d98c4 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 14 Aug 2023 11:14:56 +0300 Subject: [PATCH 68/81] fixed docs --- Packs/Tenable_io/Integrations/Tenable_io/README.md | 6 +++--- Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/README.md b/Packs/Tenable_io/Integrations/Tenable_io/README.md index 30a235000691..aa7cc98e3728 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/README.md +++ b/Packs/Tenable_io/Integrations/Tenable_io/README.md @@ -1454,7 +1454,7 @@ Lists the individual runs of the specified scan. | --- | --- | --- | | scanId | The ID of the scan of which to get the runs. | Required | | sortFields | A comma-separated list of fields by which to sort, in the order defined by "sortOrder". Possible values are: start_date, end_date, status. | Optional | -| sortOrder | A comma-separated list of direction(s) in which to sort the fields defined by "sortFields".
If multiple directions are chosen, they will be sequentially matched with "sortFields".
If only one direction is chosen it will be used to sort all values in "sortFields".
For example:
If sortFields is "start_date,status" and sortOrder is "asc,desc",
then start_date is sorted in ascending order and status in descending order.
If sortFields is "start_date,status" and sortOrder is simply "asc",
then both start_date and status are sorted in ascending order.
. Possible values are: asc, desc. Default is asc. | Optional | +| sortOrder | A comma-separated list of directions in which to sort the fields defined by "sortFields".
If multiple directions are chosen, they will be sequentially matched with "sortFields".
If only one direction is chosen it will be used to sort all values in "sortFields".
For example:
If sortFields is "start_date,status" and sortOrder is "asc,desc",
then start_date is sorted in ascending order and status in descending order.
If sortFields is "start_date,status" and sortOrder is simply "asc",
then both start_date and status are sorted in ascending order.
. Possible values are: asc, desc. Default is asc. | Optional | | excludeRollover | Whether to exclude rollover scans from the scan history. Possible values are: true, false. Default is false. | Optional | | page | The page number of scan records to retrieve (used for pagination) starting from 1. The page size is defined by the "pageSize" argument. | Optional | | pageSize | The number of scan records per page to retrieve (used for pagination). The page number is defined by the "page" argument. | Optional | @@ -1571,9 +1571,9 @@ Scans that are actively running cannot be exported (run "tenable-io-list-scans" | scanId | The identifier for the scan to export. Run the "tenable-io-list-scans" command to get all available scans. | Required | | historyId | The unique identifier of the historical data to export. Run the "tenable-io-get-scan-history" command to get history IDs. | Optional | | historyUuid | The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. | Optional | -| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
The "chapters" argument must be defined if the chosen format is HTML or PDF.
Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Optional | +| format | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days.
For scans that are older than 35 days, only the Nessus and CSV formats are supported.
The "chapters" argument must be defined if the chosen format is HTML or PDF.
. Possible values are: Nessus, HTML, PDF, CSV. Default is CSV. | Required | | chapters | A comma-separated list of chapters to include in the export. This argument is required if the file format is PDF or HTML. Possible values are: vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance. | Optional | -| filter | A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report.
Example: "port.protocol eq tcp, plugin_id eq 1234567"
Note: when used literally, commas and spaces should be escaped. (i.e. "\\\\," for comma and "\\\\s" for space)
Filters cannot be applied to scans older than 35 days.
Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response).
For more information: https://developer.tenable.com/docs/scan-export-filters-tio | Optional | +| filter | A comma-separated list of filters, in the format of "name quality value" to apply to the exported scan report.
Example: "port.protocol eq tcp, plugin_id eq 1234567"
Note: when used literally, commas and spaces should be escaped. (i.e. "\\\\," for comma and "\\\\s" for space)
Filters cannot be applied to scans older than 35 days.
Run "tenable-io-list-scan-filters" to get all available filters, ("Filter name" (name), "Filter operators" (quality) and "Readable regex" (value) in response).
For more information: https://developer.tenable.com/docs/scan-export-filters-tio
. | Optional | | filterSearchType | For multiple filters, specifies whether to use the AND or the OR logical operator. Possible values are: AND, OR. Default is AND. | Optional | | assetId | The ID of the asset scanned. | Optional | diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml index 9a4e98032631..5fcc8c85977f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.yml @@ -1127,7 +1127,7 @@ script: - status - name: sortOrder description: | - A comma-separated list of direction(s) in which to sort the fields defined by "sortFields". + A comma-separated list of directions in which to sort the fields defined by "sortFields". If multiple directions are chosen, they will be sequentially matched with "sortFields". If only one direction is chosen it will be used to sort all values in "sortFields". For example: @@ -1194,6 +1194,7 @@ script: - name: historyUuid description: The UUID of the historical data to export. Run the "tenable-io-get-scan-history" command to get history UUIDs. - name: format + required: true description: | The file format to export the scan in. Scans can be export in the HTML and PDF formats for up to 35 days. For scans that are older than 35 days, only the Nessus and CSV formats are supported. From 603c1b009d649eb21b01925d28ba7baad43c9da0 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 14 Aug 2023 14:45:02 +0300 Subject: [PATCH 69/81] test; remove demisto.results --- .../Integrations/Tenable_io/Tenable_io.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index d44d85621f2f..a9a5baf9a67b 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -343,7 +343,7 @@ def send_scan_request(scan_id="", endpoint="", method='GET', ignore_license_erro if demisto.command() != 'test-module': return_error(err_msg) else: - demisto.results(err_msg) + return_results(err_msg) demisto.error(traceback.format_exc()) sys.exit(0) except ValueError: @@ -1402,23 +1402,23 @@ def main(): # pragma: no cover command = demisto.command() if command == 'test-module': - demisto.results(test_module(client)) + return_results(test_module(client)) elif command == 'tenable-io-list-scans': - demisto.results(get_scans_command()) + return_results(get_scans_command()) elif command == 'tenable-io-launch-scan': - demisto.results(launch_scan_command()) + return_results(launch_scan_command()) elif command == 'tenable-io-get-scan-report': - demisto.results(get_report_command()) + return_results(get_report_command()) elif command == 'tenable-io-get-vulnerability-details': - demisto.results(get_vulnerability_details_command()) + return_results(get_vulnerability_details_command()) elif command == 'tenable-io-get-vulnerabilities-by-asset': - demisto.results(get_vulnerabilities_by_asset_command()) + return_results(get_vulnerabilities_by_asset_command()) elif command == 'tenable-io-get-scan-status': - demisto.results(get_scan_status_command()) + return_results(get_scan_status_command()) elif command == 'tenable-io-pause-scan': - demisto.results(pause_scan_command()) + return_results(pause_scan_command()) elif command == 'tenable-io-resume-scan': - demisto.results(resume_scan_command()) + return_results(resume_scan_command()) elif command == 'tenable-io-get-asset-details': return_results(get_asset_details_command()) elif command == 'tenable-io-export-assets': From 8104f92bfc7b2402e3edda0c3f6c805a534be8bc Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 15 Aug 2023 10:04:43 +0300 Subject: [PATCH 70/81] Revert 'test; remove demisto.results' --- .../Integrations/Tenable_io/Tenable_io.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index a9a5baf9a67b..d44d85621f2f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -343,7 +343,7 @@ def send_scan_request(scan_id="", endpoint="", method='GET', ignore_license_erro if demisto.command() != 'test-module': return_error(err_msg) else: - return_results(err_msg) + demisto.results(err_msg) demisto.error(traceback.format_exc()) sys.exit(0) except ValueError: @@ -1402,23 +1402,23 @@ def main(): # pragma: no cover command = demisto.command() if command == 'test-module': - return_results(test_module(client)) + demisto.results(test_module(client)) elif command == 'tenable-io-list-scans': - return_results(get_scans_command()) + demisto.results(get_scans_command()) elif command == 'tenable-io-launch-scan': - return_results(launch_scan_command()) + demisto.results(launch_scan_command()) elif command == 'tenable-io-get-scan-report': - return_results(get_report_command()) + demisto.results(get_report_command()) elif command == 'tenable-io-get-vulnerability-details': - return_results(get_vulnerability_details_command()) + demisto.results(get_vulnerability_details_command()) elif command == 'tenable-io-get-vulnerabilities-by-asset': - return_results(get_vulnerabilities_by_asset_command()) + demisto.results(get_vulnerabilities_by_asset_command()) elif command == 'tenable-io-get-scan-status': - return_results(get_scan_status_command()) + demisto.results(get_scan_status_command()) elif command == 'tenable-io-pause-scan': - return_results(pause_scan_command()) + demisto.results(pause_scan_command()) elif command == 'tenable-io-resume-scan': - return_results(resume_scan_command()) + demisto.results(resume_scan_command()) elif command == 'tenable-io-get-asset-details': return_results(get_asset_details_command()) elif command == 'tenable-io-export-assets': From e61bec9658d4d44a9a23df7c007288759783b018 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Thu, 17 Aug 2023 22:29:32 +0300 Subject: [PATCH 71/81] fixed TPB --- .../playbook-Tenable.io_test.yml | 104 ++++++++++++------ 1 file changed, 69 insertions(+), 35 deletions(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml index dfef6ff6ab8f..23592e70475a 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml @@ -5,10 +5,10 @@ starttaskid: "0" tasks: "0": id: "0" - taskid: f719c97e-827c-4b3a-8d90-15d4d944de7f + taskid: 2a8ea8d7-fc77-4104-8059-4de9874c0fe7 type: start task: - id: f719c97e-827c-4b3a-8d90-15d4d944de7f + id: 2a8ea8d7-fc77-4104-8059-4de9874c0fe7 version: -1 name: "" iscommand: false @@ -34,10 +34,10 @@ tasks: isautoswitchedtoquietmode: false "1": id: "1" - taskid: b4be35a2-c6c2-43f0-80df-cf78f23b2921 + taskid: e5b07211-3c08-4203-8828-b4a8449d948b type: regular task: - id: b4be35a2-c6c2-43f0-80df-cf78f23b2921 + id: e5b07211-3c08-4203-8828-b4a8449d948b version: -1 name: tenable-io-list-scans description: Retrive scans from the Tenable platform. @@ -69,10 +69,10 @@ tasks: isautoswitchedtoquietmode: false "2": id: "2" - taskid: b823f1ef-0065-4698-882a-f888eac8b9aa + taskid: 15756edb-296a-4c16-8459-6b8947f49f5a type: regular task: - id: b823f1ef-0065-4698-882a-f888eac8b9aa + id: 15756edb-296a-4c16-8459-6b8947f49f5a version: -1 name: tenable-io-get-scan-status description: 'Check the status of a specific scan using its ID. The status can @@ -107,10 +107,10 @@ tasks: isautoswitchedtoquietmode: false "3": id: "3" - taskid: 25d1d634-916b-49a4-85b1-f78ed2aa9f90 + taskid: e8e416e2-87d8-481c-81a1-7067f6ab1d71 type: regular task: - id: 25d1d634-916b-49a4-85b1-f78ed2aa9f90 + id: e8e416e2-87d8-481c-81a1-7067f6ab1d71 version: -1 name: tenable-io-get-scan-report description: Retrive scan-report for the given scan. @@ -148,10 +148,10 @@ tasks: isautoswitchedtoquietmode: false "4": id: "4" - taskid: 207d061b-ea1f-4c0e-8d49-de23521a1e8e + taskid: ccd80e24-0b34-4387-8c14-a0439031f6b3 type: regular task: - id: 207d061b-ea1f-4c0e-8d49-de23521a1e8e + id: ccd80e24-0b34-4387-8c14-a0439031f6b3 version: -1 name: DeleteContext description: Delete field from context @@ -183,10 +183,10 @@ tasks: isautoswitchedtoquietmode: false "5": id: "5" - taskid: 31fc1025-789c-4d47-811b-1b9ff3b6dc23 + taskid: 97d4cdbe-354d-4cf5-815e-526cc33af179 type: regular task: - id: 31fc1025-789c-4d47-811b-1b9ff3b6dc23 + id: 97d4cdbe-354d-4cf5-815e-526cc33af179 version: -1 name: tenable-io-get-vulnerability-details description: Retrieve details for the given vulnerability. @@ -222,10 +222,10 @@ tasks: isautoswitchedtoquietmode: false "6": id: "6" - taskid: 002b1bc8-8bf1-4852-8db3-50acdbf66267 + taskid: c3ea6b10-1adb-4539-85e1-f897d2a1ccfe type: regular task: - id: 002b1bc8-8bf1-4852-8db3-50acdbf66267 + id: c3ea6b10-1adb-4539-85e1-f897d2a1ccfe version: -1 name: tenable-io-get-vulnerabilities-by-asset description: Get a list of up to 5000 of the vulnerabilities recorded for a @@ -260,10 +260,10 @@ tasks: isautoswitchedtoquietmode: false "7": id: "7" - taskid: c1497579-2385-45af-8b68-6187baad6227 + taskid: 09a70694-12f6-44e1-86b3-76371d172e36 type: regular task: - id: c1497579-2385-45af-8b68-6187baad6227 + id: 09a70694-12f6-44e1-86b3-76371d172e36 version: -1 name: Tenable-io-export-assets description: Retrieves details for the specified asset to include custom attributes. @@ -297,10 +297,10 @@ tasks: isautoswitchedtoquietmode: false "8": id: "8" - taskid: 4d6e868c-7d5f-4d0c-80ab-9178c72a2b8b + taskid: b0488d35-5a4b-4b3e-858d-43c54d28a888 type: regular task: - id: 4d6e868c-7d5f-4d0c-80ab-9178c72a2b8b + id: b0488d35-5a4b-4b3e-858d-43c54d28a888 version: -1 name: Tenable-io-export-vulnerability description: Retrieves details for the specified asset to include custom attributes. @@ -332,10 +332,10 @@ tasks: isautoswitchedtoquietmode: false "9": id: "9" - taskid: f759ff5e-828d-48c6-8d5a-332fc4527923 + taskid: b1353dec-47ad-4833-8ae8-fd2942d5d376 type: condition task: - id: f759ff5e-828d-48c6-8d5a-332fc4527923 + id: b1353dec-47ad-4833-8ae8-fd2942d5d376 version: -1 name: Verify vulnerabilities type: condition @@ -370,10 +370,10 @@ tasks: isautoswitchedtoquietmode: false "10": id: "10" - taskid: fcc96894-3cc8-4b31-8bd3-b08aef458cde + taskid: e9b3a100-5e5b-428c-877a-26dd3fb8b942 type: condition task: - id: fcc96894-3cc8-4b31-8bd3-b08aef458cde + id: e9b3a100-5e5b-428c-877a-26dd3fb8b942 version: -1 name: Verify assets type: condition @@ -408,10 +408,10 @@ tasks: isautoswitchedtoquietmode: false "11": id: "11" - taskid: f00cabbd-de80-45b2-890d-61c82f8dcabf + taskid: 1ce016d5-80af-4005-8dbf-8785b39c121e type: title task: - id: f00cabbd-de80-45b2-890d-61c82f8dcabf + id: 1ce016d5-80af-4005-8dbf-8785b39c121e version: -1 name: Done type: title @@ -435,10 +435,10 @@ tasks: isautoswitchedtoquietmode: false "12": id: "12" - taskid: ee1b63da-40f0-4102-86a2-cf36557f9c89 + taskid: e06b064b-c26d-4125-8ee3-c2111dc11878 type: regular task: - id: ee1b63da-40f0-4102-86a2-cf36557f9c89 + id: e06b064b-c26d-4125-8ee3-c2111dc11878 version: -1 name: Run get-scan-history Command description: Lists the individual runs of the specified scan. @@ -453,7 +453,7 @@ tasks: excludeRollover: simple: "true" limit: - simple: "1" + simple: "20" scanId: complex: root: TenableIO.Scan @@ -495,10 +495,10 @@ tasks: isautoswitchedtoquietmode: false "13": id: "13" - taskid: 9c7d1767-1f11-428a-8253-7105527f8083 + taskid: 4d2d7fbd-eb29-42b7-84e6-b692d3e3ba4e type: regular task: - id: 9c7d1767-1f11-428a-8253-7105527f8083 + id: 4d2d7fbd-eb29-42b7-84e6-b692d3e3ba4e version: -1 name: Run list-scan-filters Command description: Lists the filtering, sorting, and pagination capabilities available @@ -528,10 +528,10 @@ tasks: isautoswitchedtoquietmode: false "14": id: "14" - taskid: 0883f54e-9123-4368-835d-f6223420de1e + taskid: cc10f48a-204b-4a51-8e71-69ded5e54112 type: regular task: - id: 0883f54e-9123-4368-835d-f6223420de1e + id: cc10f48a-204b-4a51-8e71-69ded5e54112 version: -1 name: Run export-scan Command description: Export and download a scan report. @@ -548,9 +548,43 @@ tasks: format: simple: CSV historyId: - simple: ${TenableIO.ScanHistory.id} + complex: + root: TenableIO.ScanHistory + filters: + - - operator: isEqualString + left: + value: + simple: TenableIO.ScanHistory.status + iscontext: true + right: + value: + simple: completed + accessor: id + transformers: + - operator: atIndex + args: + index: + value: + simple: "0" historyUuid: - simple: ${TenableIO.ScanHistory.scan_uuid} + complex: + root: TenableIO.ScanHistory + filters: + - - operator: isEqualString + left: + value: + simple: TenableIO.ScanHistory.status + iscontext: true + right: + value: + simple: completed + accessor: scan_uuid + transformers: + - operator: atIndex + args: + index: + value: + simple: "0" scanId: complex: root: TenableIO.Scan @@ -588,10 +622,10 @@ tasks: isautoswitchedtoquietmode: false "15": id: "15" - taskid: fe841822-b4d5-4598-8c4d-115c7308d9be + taskid: 0679e8ba-cb2e-4050-8c24-39861f642962 type: condition task: - id: fe841822-b4d5-4598-8c4d-115c7308d9be + id: 0679e8ba-cb2e-4050-8c24-39861f642962 version: -1 name: Verify list scan filters type: condition From fd7ed35f96a33e7674143cf69deff18f24a060c7 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Fri, 18 Aug 2023 08:16:54 +0300 Subject: [PATCH 72/81] test; table in error response --- .../Integrations/Tenable_io/Tenable_io.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index d44d85621f2f..09b0169a01ee 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -162,6 +162,20 @@ class Client(BaseClient): + def client_error_handler(self, res): + try: + msg = res.json() + return_results(CommandResults( + entry_type=EntryType.ERROR, + readable_output=tableToMarkdown( + f'Error in Tenable.io API call [{res.status_code}] - {res.reason}\n', + msg, list(msg), lambda x: x.title() + ) + )) + sys.exit(0) + except ValueError: + super().client_error_handler(res) + def list_scan_filters(self): return self._http_request( 'GET', 'filters/scans/reports') From 043b0797d147084d895c1343cee25b36786770c6 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Fri, 18 Aug 2023 08:26:48 +0300 Subject: [PATCH 73/81] revert: 'test; table in error response' --- .../Integrations/Tenable_io/Tenable_io.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index 09b0169a01ee..d44d85621f2f 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -162,20 +162,6 @@ class Client(BaseClient): - def client_error_handler(self, res): - try: - msg = res.json() - return_results(CommandResults( - entry_type=EntryType.ERROR, - readable_output=tableToMarkdown( - f'Error in Tenable.io API call [{res.status_code}] - {res.reason}\n', - msg, list(msg), lambda x: x.title() - ) - )) - sys.exit(0) - except ValueError: - super().client_error_handler(res) - def list_scan_filters(self): return self._http_request( 'GET', 'filters/scans/reports') From 1c3ca92d7203b9ea0ec85cc59b66e4f8c052b6aa Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Fri, 18 Aug 2023 12:30:43 +0300 Subject: [PATCH 74/81] cosmetic changes --- .../Integrations/Tenable_io/Tenable_io.py | 14 ++++---------- .../Integrations/Tenable_io/Tenable_io_test.py | 11 ++++++----- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py index d44d85621f2f..5fafb9abad37 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io.py @@ -1093,16 +1093,10 @@ def validate_range(range: Optional[str]) -> tuple[Optional[float], Optional[floa Range if valid else raise DemistoException. """ if range: - range_without_spaces = range.strip() - nums_list = range_without_spaces.split("-") - if len(nums_list) < 2: - raise DemistoException('Please specify valid vprScoreRange. VPR values range are 0.1-10.0.') - elif float(nums_list[0]) > float(nums_list[1]): - raise DemistoException('Please specify valid vprScoreRange. VPR values range are 0.1-10.0.') - elif float(nums_list[0]) < 0.1 or float(nums_list[1]) > 10.0: - raise DemistoException('Please specify valid vprScoreRange. VPR values range are 0.1-10.0.') - else: - return float(nums_list[0]), float(nums_list[1]) + nums = tuple(map(float, range.split("-"))) + if len(nums) != 2 or not 0.1 <= nums[0] <= nums[1] <= 10.0: + raise DemistoException('Please specify a valid vprScoreRange. The VPR values range is 0.1-10.0.') + return nums # type: ignore return None, None diff --git a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py index 38df1818630b..45e3c97b5e73 100644 --- a/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py +++ b/Packs/Tenable_io/Integrations/Tenable_io/Tenable_io_test.py @@ -195,6 +195,8 @@ def test_get_asset_details_command(mocker, requests_mock): ('2.5-3.5', 2.5, 3.5), ('0.1-3', 0.1, 3), ('0.1 - 3', 0.1, 3), + ('0-1', 'exception', 'exception'), + ('3-100', 'exception', 'exception'), ('0', 'exception', 'exception'), ('3-0', 'exception', 'exception') ]) @@ -209,11 +211,10 @@ def test_validate_range(range_str, expected_lower_range_bound, expected_upper_ra """ from Tenable_io import validate_range - if isinstance(expected_lower_range_bound, str) and isinstance(expected_upper_range_bound, str): - with pytest.raises(DemistoException) as de: + if expected_lower_range_bound == expected_upper_range_bound == 'exception': + err_msg = 'Please specify a valid vprScoreRange. The VPR values range is 0.1-10.0.' + with pytest.raises(DemistoException, match=err_msg): lower_range_bound, upper_range_bound = validate_range(range_str) - - assert de.value.message == 'Please specify valid vprScoreRange. VPR values range are 0.1-10.0.' else: lower_range_bound, upper_range_bound = validate_range(range_str) assert lower_range_bound == expected_lower_range_bound @@ -364,7 +365,7 @@ def test_export_assets_command(mocker, args, return_value_export_request_with_ex from Tenable_io import export_assets_command import Tenable_io from test_data.response_and_results import export_assets_response - mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported', return_value=None) + mocker.patch.object(ScheduledCommand, 'raise_error_if_not_supported') mocker.patch.object(Tenable_io, 'export_request', return_value={"export_uuid": 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'}) mocker.patch.object(Tenable_io, 'export_request_with_export_uuid', return_value={"export_uuid": 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'}) From c8d38bcb461f78797e4d658de42f4b27875b2dbc Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 21 Aug 2023 16:35:22 +0300 Subject: [PATCH 75/81] TPB on quick scan --- .../playbook-Tenable.io_Scan_Test.yml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml index f0f264b8bf35..587541f4db18 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml @@ -82,24 +82,7 @@ tasks: brand: "" scriptarguments: scan-id: - complex: - root: TenableIO.Scan - filters: - - - operator: isEqualString - left: - value: - simple: TenableIO.Scan.Status - iscontext: true - right: - value: - simple: completed - accessor: Id - transformers: - - operator: atIndex - args: - index: - value: - simple: "0" + simple: "55" separatecontext: true continueonerrortype: "" loop: From 2eba53be7b3c9052d35965ce5e7a012fedc41310 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 21 Aug 2023 18:36:31 +0300 Subject: [PATCH 76/81] TPB timeout fix --- Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml index 23592e70475a..631d34e6154f 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml @@ -313,7 +313,9 @@ tasks: - "9" scriptarguments: numAssets: - simple: "500" + simple: "50" + timeOut: + simple: "120" separatecontext: false continueonerrortype: "" view: |- From 4543d973406f9545f1cbe77ba634a1ed6abbf2b0 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Mon, 21 Aug 2023 23:24:05 +0300 Subject: [PATCH 77/81] TPB launch in unallowed status fix --- .../playbook-Tenable.io_Scan_Test.yml | 104 +++++++++++++++--- 1 file changed, 87 insertions(+), 17 deletions(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml index 587541f4db18..ff10a37f2ca9 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml @@ -5,19 +5,19 @@ starttaskid: "0" tasks: "0": id: "0" - taskid: cce5c0b6-d002-46e7-8ec6-ec50bc150e19 + taskid: e5bcdce1-05b5-40be-8a73-c38ae3dbee9f type: start task: - id: cce5c0b6-d002-46e7-8ec6-ec50bc150e19 + id: e5bcdce1-05b5-40be-8a73-c38ae3dbee9f version: -1 name: "" iscommand: false brand: "" + description: '' nexttasks: '#none#': - "2" separatecontext: false - continueonerrortype: "" view: |- { "position": { @@ -26,6 +26,7 @@ tasks: } } note: false + continueonerrortype: "" timertriggers: [] ignoreworker: false skipunavailable: false @@ -34,10 +35,10 @@ tasks: isautoswitchedtoquietmode: false "2": id: "2" - taskid: 663f0dbb-894b-4073-87ff-a5e77c43a162 + taskid: ed65bc4b-8534-4ee5-8eb2-9be5a3cae6d7 type: regular task: - id: 663f0dbb-894b-4073-87ff-a5e77c43a162 + id: ed65bc4b-8534-4ee5-8eb2-9be5a3cae6d7 version: -1 name: DeleteContext description: Delete field from context @@ -52,15 +53,15 @@ tasks: all: simple: "yes" separatecontext: false - continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 195 + "y": 175 } } note: false + continueonerrortype: "" timertriggers: [] ignoreworker: false skipunavailable: false @@ -69,22 +70,23 @@ tasks: isautoswitchedtoquietmode: false "3": id: "3" - taskid: facabc59-d0bb-464f-8745-62ffcac89667 + taskid: bc7d35a9-3cdc-4724-8bab-0a8dbe1648d3 type: playbook task: - id: facabc59-d0bb-464f-8745-62ffcac89667 + id: bc7d35a9-3cdc-4724-8bab-0a8dbe1648d3 version: -1 name: Tenable.io Scan - description: Run a Tenable.io scan playbookName: Tenable.io Scan type: playbook iscommand: false brand: "" + description: '' scriptarguments: + retry-count: + simple: "2" scan-id: simple: "55" separatecontext: true - continueonerrortype: "" loop: iscommand: false exitCondition: "" @@ -94,7 +96,75 @@ tasks: { "position": { "x": 50, - "y": 545 + "y": 650 + } + } + note: false + continueonerrortype: "" + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "4": + id: "4" + taskid: 6174091e-cfc1-4fc4-87ea-115a501e6c53 + type: playbook + task: + id: 6174091e-cfc1-4fc4-87ea-115a501e6c53 + version: -1 + name: GenericPolling + description: |- + Use this playbook as a sub-playbook to block execution of the master playbook until a remote action is complete. + This playbook implements polling by continuously running the command in Step \#2 until the operation completes. + The remote action should have the following structure: + + 1. Initiate the operation. + 2. Poll to check if the operation completed. + 3. (optional) Get the results of the operation. + playbookName: GenericPolling + type: playbook + iscommand: false + brand: "" + nexttasks: + '#none#': + - "3" + scriptarguments: + Ids: + complex: + root: TenableIO.Scan.Id + filters: + - - operator: containsGeneral + left: + value: + simple: TenableIO.Scan.Id + iscontext: true + right: + value: + simple: "55" + Interval: + simple: "1" + PollingCommandArgName: + simple: scanId + PollingCommandName: + simple: tenable-io-get-scan-status + Timeout: + simple: "30" + dt: + simple: TenableIO.Scan(val.Status == 'pending' || val.Status == 'running' || val.Status == 'publishing').Id + separatecontext: true + continueonerrortype: "" + loop: + iscommand: false + exitCondition: "" + wait: 1 + max: 100 + view: |- + { + "position": { + "x": 50, + "y": 490 } } note: false @@ -106,10 +176,10 @@ tasks: isautoswitchedtoquietmode: false "5": id: "5" - taskid: 733425c5-96e1-4ddb-8cc1-f43fe6de828e + taskid: 906f1daf-f844-4d7c-884a-8892b71ca63e type: regular task: - id: 733425c5-96e1-4ddb-8cc1-f43fe6de828e + id: 906f1daf-f844-4d7c-884a-8892b71ca63e version: -1 name: 'tenable.io list scans ' description: Retrieves scans from the Tenable platform. @@ -119,14 +189,14 @@ tasks: brand: "" nexttasks: '#none#': - - "3" + - "4" separatecontext: false continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 370 + "y": 340 } } note: false @@ -141,7 +211,7 @@ view: |- "linkLabelsPosition": {}, "paper": { "dimensions": { - "height": 590, + "height": 695, "width": 380, "x": 50, "y": 50 From 5e47cf778b19a3375a0a4ec0ae6e355721e5acc6 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 22 Aug 2023 00:48:21 +0300 Subject: [PATCH 78/81] TPB launch in unallowed status fix --- .../playbook-Tenable.io_Scan_Test.yml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml index ff10a37f2ca9..3a28bfe8f246 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml @@ -5,10 +5,10 @@ starttaskid: "0" tasks: "0": id: "0" - taskid: e5bcdce1-05b5-40be-8a73-c38ae3dbee9f + taskid: cad70594-65e7-4983-8d68-7edf282b4c1f type: start task: - id: e5bcdce1-05b5-40be-8a73-c38ae3dbee9f + id: cad70594-65e7-4983-8d68-7edf282b4c1f version: -1 name: "" iscommand: false @@ -35,10 +35,10 @@ tasks: isautoswitchedtoquietmode: false "2": id: "2" - taskid: ed65bc4b-8534-4ee5-8eb2-9be5a3cae6d7 + taskid: b69f20c4-3994-45ef-8930-ed1404fd2dc0 type: regular task: - id: ed65bc4b-8534-4ee5-8eb2-9be5a3cae6d7 + id: b69f20c4-3994-45ef-8930-ed1404fd2dc0 version: -1 name: DeleteContext description: Delete field from context @@ -70,10 +70,10 @@ tasks: isautoswitchedtoquietmode: false "3": id: "3" - taskid: bc7d35a9-3cdc-4724-8bab-0a8dbe1648d3 + taskid: f6fbf897-9776-43f0-833d-10ab0e329430 type: playbook task: - id: bc7d35a9-3cdc-4724-8bab-0a8dbe1648d3 + id: f6fbf897-9776-43f0-833d-10ab0e329430 version: -1 name: Tenable.io Scan playbookName: Tenable.io Scan @@ -109,10 +109,10 @@ tasks: isautoswitchedtoquietmode: false "4": id: "4" - taskid: 6174091e-cfc1-4fc4-87ea-115a501e6c53 + taskid: e5015b9f-912e-40e5-8e25-03f450040f46 type: playbook task: - id: 6174091e-cfc1-4fc4-87ea-115a501e6c53 + id: e5015b9f-912e-40e5-8e25-03f450040f46 version: -1 name: GenericPolling description: |- @@ -135,7 +135,7 @@ tasks: complex: root: TenableIO.Scan.Id filters: - - - operator: containsGeneral + - - operator: isEqualNumber left: value: simple: TenableIO.Scan.Id @@ -152,7 +152,7 @@ tasks: Timeout: simple: "30" dt: - simple: TenableIO.Scan(val.Status == 'pending' || val.Status == 'running' || val.Status == 'publishing').Id + simple: TenableIO.Scan(val.Status != 'pending' && val.Status != 'running' && val.Status != 'publishing').Id separatecontext: true continueonerrortype: "" loop: @@ -176,10 +176,10 @@ tasks: isautoswitchedtoquietmode: false "5": id: "5" - taskid: 906f1daf-f844-4d7c-884a-8892b71ca63e + taskid: 9db82c06-1ac7-4528-8a52-b3f745346688 type: regular task: - id: 906f1daf-f844-4d7c-884a-8892b71ca63e + id: 9db82c06-1ac7-4528-8a52-b3f745346688 version: -1 name: 'tenable.io list scans ' description: Retrieves scans from the Tenable platform. From 423da167e092759f2c3177e67c9bde430b7d6cf2 Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 22 Aug 2023 09:22:50 +0300 Subject: [PATCH 79/81] TPB --- .../playbook-Tenable.io_Scan_Test.yml | 41 +++++++-------- .../playbook-Tenable.io_test.yml | 51 +++++++++---------- 2 files changed, 42 insertions(+), 50 deletions(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml index 3a28bfe8f246..532a43bf3711 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_Scan_Test.yml @@ -57,7 +57,7 @@ tasks: { "position": { "x": 50, - "y": 175 + "y": 195 } } note: false @@ -96,7 +96,7 @@ tasks: { "position": { "x": 50, - "y": 650 + "y": 720 } } note: false @@ -109,10 +109,10 @@ tasks: isautoswitchedtoquietmode: false "4": id: "4" - taskid: e5015b9f-912e-40e5-8e25-03f450040f46 + taskid: 5d13467d-5039-4ceb-8d80-a11777c6c54f type: playbook task: - id: e5015b9f-912e-40e5-8e25-03f450040f46 + id: 5d13467d-5039-4ceb-8d80-a11777c6c54f version: -1 name: GenericPolling description: |- @@ -132,17 +132,7 @@ tasks: - "3" scriptarguments: Ids: - complex: - root: TenableIO.Scan.Id - filters: - - - operator: isEqualNumber - left: - value: - simple: TenableIO.Scan.Id - iscontext: true - right: - value: - simple: "55" + simple: "55" Interval: simple: "1" PollingCommandArgName: @@ -152,7 +142,7 @@ tasks: Timeout: simple: "30" dt: - simple: TenableIO.Scan(val.Status != 'pending' && val.Status != 'running' && val.Status != 'publishing').Id + simple: TenableIO.Scan(val.Status != 'completed').Id separatecontext: true continueonerrortype: "" loop: @@ -164,7 +154,7 @@ tasks: { "position": { "x": 50, - "y": 490 + "y": 545 } } note: false @@ -176,14 +166,14 @@ tasks: isautoswitchedtoquietmode: false "5": id: "5" - taskid: 9db82c06-1ac7-4528-8a52-b3f745346688 + taskid: e1f6435c-3757-40d3-83c4-35ae1463ceea type: regular task: - id: 9db82c06-1ac7-4528-8a52-b3f745346688 + id: e1f6435c-3757-40d3-83c4-35ae1463ceea version: -1 - name: 'tenable.io list scans ' - description: Retrieves scans from the Tenable platform. - script: '|||tenable-io-list-scans' + name: 'Get scan status' + description: 'Checks the status of a specific scan using the scan ID. Possible values: "Running", "Completed", and "Empty" (Ready to run).' + script: '|||tenable-io-get-scan-status' type: regular iscommand: true brand: "" @@ -196,7 +186,7 @@ tasks: { "position": { "x": 50, - "y": 340 + "y": 370 } } note: false @@ -206,12 +196,15 @@ tasks: quietmode: 0 isoversize: false isautoswitchedtoquietmode: false + scriptarguments: + scanId: + simple: "55" view: |- { "linkLabelsPosition": {}, "paper": { "dimensions": { - "height": 695, + "height": 765, "width": 380, "x": 50, "y": 50 diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml index 631d34e6154f..8ecae4d7965f 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml @@ -13,11 +13,11 @@ tasks: name: "" iscommand: false brand: "" + description: '' nexttasks: '#none#': - "4" separatecontext: false - continueonerrortype: "" view: |- { "position": { @@ -30,6 +30,7 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 + continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "1": @@ -52,7 +53,6 @@ tasks: retry-count: simple: "2" separatecontext: false - continueonerrortype: "" view: |- { "position": { @@ -65,6 +65,7 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 + continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "2": @@ -75,8 +76,7 @@ tasks: id: 15756edb-296a-4c16-8459-6b8947f49f5a version: -1 name: tenable-io-get-scan-status - description: 'Check the status of a specific scan using its ID. The status can - hold following possible values : Running, Completed and Empty (Ready to run).' + description: 'Check the status of a specific scan using its ID. The status can hold following possible values : Running, Completed and Empty (Ready to run).' script: '|||tenable-io-get-scan-status' type: regular iscommand: true @@ -90,7 +90,6 @@ tasks: scanId: simple: "16" separatecontext: false - continueonerrortype: "" view: |- { "position": { @@ -103,6 +102,7 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 + continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "3": @@ -122,16 +122,15 @@ tasks: '#none#': - "6" scriptarguments: + retry-count: + simple: "2" detailed: simple: "yes" info: simple: "yes" - retry-count: - simple: "2" scanId: simple: "16" separatecontext: false - continueonerrortype: "" view: |- { "position": { @@ -144,6 +143,7 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 + continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "4": @@ -166,7 +166,6 @@ tasks: all: simple: "yes" separatecontext: false - continueonerrortype: "" view: |- { "position": { @@ -179,6 +178,7 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 + continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "5": @@ -194,9 +194,6 @@ tasks: type: regular iscommand: true brand: "" - nexttasks: - '#none#': - - "7" scriptarguments: retry-count: simple: "2" @@ -205,7 +202,6 @@ tasks: root: TenableIO accessor: Vulnerabilities.[0].Id separatecontext: false - continueonerrortype: "" view: |- { "position": { @@ -218,6 +214,10 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 + nexttasks: + '#none#': + - "7" + continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "6": @@ -228,8 +228,7 @@ tasks: id: c3ea6b10-1adb-4539-85e1-f897d2a1ccfe version: -1 name: tenable-io-get-vulnerabilities-by-asset - description: Get a list of up to 5000 of the vulnerabilities recorded for a - given asset. By default, this list is sorted by vulnerability count, descending. + description: Get a list of up to 5000 of the vulnerabilities recorded for a given asset. By default, this list is sorted by vulnerability count, descending. script: '|||tenable-io-get-vulnerabilities-by-asset' type: regular iscommand: true @@ -238,12 +237,11 @@ tasks: '#none#': - "5" scriptarguments: - hostname: - simple: ec2-52-50-45-109.eu-west-1.compute.amazonaws.com retry-count: simple: "2" + hostname: + simple: ec2-52-50-45-109.eu-west-1.compute.amazonaws.com separatecontext: false - continueonerrortype: "" view: |- { "position": { @@ -256,14 +254,15 @@ tasks: ignoreworker: false skipunavailable: false quietmode: 0 + continueonerrortype: "" isoversize: false isautoswitchedtoquietmode: false "7": id: "7" - taskid: 09a70694-12f6-44e1-86b3-76371d172e36 + taskid: 8668f8d2-3007-4ee9-81fd-c1e8752d2c3e type: regular task: - id: 09a70694-12f6-44e1-86b3-76371d172e36 + id: 8668f8d2-3007-4ee9-81fd-c1e8752d2c3e version: -1 name: Tenable-io-export-assets description: Retrieves details for the specified asset to include custom attributes. @@ -276,9 +275,11 @@ tasks: - "10" scriptarguments: chunkSize: - simple: "500" + simple: "100" serviceNowSysId: simple: "false" + retry-count: + simple: "5" separatecontext: false continueonerrortype: "" view: |- @@ -313,9 +314,7 @@ tasks: - "9" scriptarguments: numAssets: - simple: "50" - timeOut: - simple: "120" + simple: "500" separatecontext: false continueonerrortype: "" view: |- @@ -419,6 +418,7 @@ tasks: type: title iscommand: false brand: "" + description: '' separatecontext: false continueonerrortype: "" view: |- @@ -503,8 +503,7 @@ tasks: id: 4d2d7fbd-eb29-42b7-84e6-b692d3e3ba4e version: -1 name: Run list-scan-filters Command - description: Lists the filtering, sorting, and pagination capabilities available - for scan records on endpoints/commands that support them. + description: Lists the filtering, sorting, and pagination capabilities available for scan records on endpoints/commands that support them. script: Tenable.io|||tenable-io-list-scan-filters type: regular iscommand: true From 7dfd8677abf98aa8c94654b80d1bb67209d6145d Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 22 Aug 2023 13:08:07 +0300 Subject: [PATCH 80/81] TPB --- Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml index 8ecae4d7965f..b8202cbccc78 100644 --- a/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml +++ b/Packs/Tenable_io/TestPlaybooks/playbook-Tenable.io_test.yml @@ -314,7 +314,9 @@ tasks: - "9" scriptarguments: numAssets: - simple: "500" + simple: "50" + timeOut: + simple: "120" separatecontext: false continueonerrortype: "" view: |- From 095684843619a29cfbcc16ce751cbe490c590a9e Mon Sep 17 00:00:00 2001 From: jlevypaloalto Date: Tue, 22 Aug 2023 16:22:06 +0300 Subject: [PATCH 81/81] extended timeout --- Tests/conf.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/conf.json b/Tests/conf.json index 0319e97ae92a..ef745e227e2f 100644 --- a/Tests/conf.json +++ b/Tests/conf.json @@ -1207,7 +1207,8 @@ }, { "integrations": "Tenable.io", - "playbookID": "Tenable.io test" + "playbookID": "Tenable.io test", + "timeout": 3600 }, { "playbookID": "URLDecode-Test"